cucumber事始め

日本語で結合(総合)テストが書けるcucumberを触ってみました。
form入力の動作をラベル名で指定できたり、表形式でのデータ表現はとてもいいと思いました。


いきなりガシガシ書けるというモノでは無いので
日本語で書くことを理解するために自動生成されるテストを
日本語に変更してみました。


なので、以下実践的な内容ではありません。
scaffold程度の画面では不要で、もっと複雑なシナリオの方が向いていると思います。

インストール

Ruby on Rails - cucumber - GitHub
rails で使うには、cucumber-railsが必要です。
また、moroさん作成のmoro-miso(もろ味噌)も入れましょう。

$ sudo gem install cucumber
$ sudo gem install cucumber-rails
$ sudo gem install moro-miso
$ rails blog
$ cd blog
$ ruby script/generate cucumber --webrat
       force  config/database.yml
      create  config/cucumber.yml
      create  config/environments/cucumber.rb
      create  script/cucumber
      create  features/step_definitions
      create  features/step_definitions/web_steps.rb
      create  features/support
      create  features/support/paths.rb
      create  features/support/env.rb
      exists  lib/tasks
      create  lib/tasks/cucumber.rake

webratオプション無しだと警告が出ました。
webrat/capybara/rspec/testunit/sport が選択可能のようです。

(( : : )が点滅してます。どうやってんの?)

sudo rake RAILS_ENV=cucumber gems:install

何コレと思って、config/environments/cucumber.rb を覗くと

config.gem 'cucumber-rails',   :lib => false, :version => '>=0.3.1' unless File.directory?(File.join(Rails.root, 'vendor/plugins/cucumber-rails'))
config.gem 'database_cleaner', :lib => false, :version => '>=0.5.0' unless File.directory?(File.join(Rails.root, 'vendor/plugins/database_cleaner'))
config.gem 'webrat',           :lib => false, :version => '>=0.7.0' unless File.directory?(File.join(Rails.root, 'vendor/plugins/webrat'))

なるほどなるほど。


自動生成

$ ruby script/generate rspec_scaffold Article title:string content:text
$ ruby script/generate feature Article title:string content:text
$ ruby script/generate miso
      exists  features/step_definitions
      create  features/step_definitions/webrat_ja_steps.rb
      create  features/step_definitions/web_extra_ja_steps.rb
$ rake db:migrate
$ rake cucumber
.........

2 scenarios (2 passed)
9 steps (9 passed)
0m0.370s

misoで生成されるファイルが日本語の素です。
cucumberの実行は、rake cucumberの他にもcucumberでも可。

features以下の構成はこんな感じ

今回いじったのは、下記コメントのファイル。

$ tree features/
features/
|-- manage_articles.feature       # ここにテストシナリオを書きます。
|-- step_definitions
|   |-- article_steps.rb          # シナリオ内での実際の動作を書きます。
|   |-- web_extra_ja_steps.rb
|   |-- web_steps.rb
|   `-- webrat_ja_steps.rb
`-- support
    |-- env.rb
    `-- paths.rb                  # ここにパス情報を書きます。
#features/manage_articles.feature
Feature: Manage articles
  In order to [goal]
  [stakeholder]
  wants [behaviour]
  
  Scenario: Register new article
    Given I am on the new article page
    When I fill in "Title" with "title 1"
    And I fill in "Content" with "content 1"
    And I press "Create"
    Then I should see "title 1"
    And I should see "content 1"

  Scenario: Delete article
    Given the following articles:
      |title|content|
      |title 1|content 1|
      |title 2|content 2|
      |title 3|content 3|
      |title 4|content 4|
    When I delete the 3rd article
    Then I should see the following articles:
      |Title|Content|
      |title 1|content 1|
      |title 2|content 2|
      |title 4|content 4|
      

#step_definitions/article_steps.rb
Given /^the following articles:$/ do |articles|
  Article.create!(articles.hashes)
end

When /^I delete the (\d+)(?:st|nd|rd|th) article$/ do |pos|
  visit articles_path
  within("table tr:nth-child(#{pos.to_i+1})") do
    click_link "Destroy"
  end
end

Then /^I should see the following articles:$/ do |expected_articles_table|
  expected_articles_table.diff!(tableish('table tr', 'td,th'))
end

日本語化

まずはFeatureから

Feature: Manage articles
  管理者は、ブログの記事を管理できる

ここは何でもいいんですが、
元の英語のようにgoal/stakeholder/behaviorを意識した方がいいでしょう。
cucumberを再実行。問題ありません。


続いてScenario名

Scenario: 記事を登録する

ここも問題無し。


次はGiven。

    Given I am on the new article page


仕組みはこんな感じ。

#step_definitions/web_steps.rb
Given /^(?:|I )am on (.+)$/ do |page_name|
  visit path_to(page_name)
end

#support/paths.rb
  def path_to(page_name)
    case page_name

    when /the home\s?page/
      '/'

    when /the new article page/
      new_article_path


webrat_ja_steps.rbで

visit = lambda{|page_name| Given "I am on #{page_name}" }

Given(/^"([^\"]*)"ページを表示している$/, &visit)

となっているので、こんな風に置き換えれます。

#support/paths.rb
    when /新規記事/
      new_article_path


#features/manage_articles.feature
    Given "新規記事"ページを表示している


他も同様にwebrat_ja_steps.rbとweb_steps.rbを見ると
日本語に置き換えれます。


こんな感じになりました。

#features/manage_articles.feature
Feature: Manage articles
  管理者は、ブログの記事を管理できる
  
  Scenario: 記事を登録する
    Given "新規記事"ページを表示している
    When  "Title""title 1"と入力する
    And   "Content""content 1"と入力する
    And   "Create"ボタンをクリックする
    Then  "title 1"と表示されていること
    And   "content 1"と表示されていること

  Scenario: 記事を削除する
    Given 以下の記事を作成する:
      |title|content|
      |タイトル 1|コンテンツ 1|
      |タイトル 2|コンテンツ 2|
      |タイトル 3|コンテンツ 3|
      |タイトル 4|コンテンツ 4|
    When 3番目の記事を削除する
    Then 以下の記事を表示していること:
      |Title|Content|
      |タイトル 1|コンテンツ 1|
      |タイトル 2|コンテンツ 2|
      |タイトル 4|コンテンツ 4|

#step_definitions/article_steps.rb
Given /^以下の記事を作成する:$/ do |articles|
  Article.create!(articles.hashes)
end

When /^(\d+)番目の記事を削除する$/ do |pos|
  visit articles_path
  within("table tr:nth-child(#{pos.to_i+1})") do
    click_link "Destroy"
  end
end

Then /^以下の記事を表示していること:$/ do |expected_articles_table|
  expected_articles_table.diff!(tableish('table tr', 'td,th'))
end

ほとんど日本語ですね。これでもテストが通ります。

最後に

テスト内に残っている英語(Title/Content/Createボタン/Destroy)を変更します。
ここは、scaffoldで生成される箇所です。


こうなりました。

#features/manage_articles.feature
Feature: Manage articles
  管理者は、ブログの記事を管理できる
  
  Scenario: 記事を登録する
    Given "新規記事"ページを表示している
    When  "タイトル""タイトル 1"と入力する
    And   "コンテンツ""コンテンツ 1"と入力する
    And   "送信"ボタンをクリックする
    Then  "タイトル 1"と表示されていること
    And   "コンテンツ 1"と表示されていること

  Scenario: 記事を削除する
    Given 以下の記事を作成する:
      |title|content|
      |タイトル 1|コンテンツ 1|
      |タイトル 2|コンテンツ 2|
      |タイトル 3|コンテンツ 3|
      |タイトル 4|コンテンツ 4|
    When 3番目の記事を削除する
    Then 以下の記事を表示していること:
      |Title|Content|
      |タイトル 1|コンテンツ 1|
      |タイトル 2|コンテンツ 2|
      |タイトル 4|コンテンツ 4|

#step_definitions/article_steps.rb
Given /^以下の記事を作成する:$/ do |articles|
  Article.create!(articles.hashes)
end

When /^(\d+)番目の記事を削除する$/ do |pos|
  visit articles_path
  within("table tr:nth-child(#{pos.to_i+1})") do
    click_link "削除"
  end
end

Then /^以下の記事を表示していること:$/ do |expected_articles_table|
  expected_articles_table.diff!(tableish('table tr', 'td,th'))
end


ここで再実行すると、「タイトル」って何って怒られます。

.F----...

(::) failed steps (::)

Could not find field: "タイトル" (Webrat::NotFoundError)
(eval):2:in `fill_in'
./features/step_definitions/web_steps.rb:36:in `/^(?:|I )fill in "([^\"]*)" with "([^\"]*)"$/'
features/manage_articles.feature:6:in `When "タイトル"に"タイトル 1"と入力する'

Failing Scenarios:
cucumber features/manage_articles.feature:4 # Scenario: 記事を登録する

2 scenarios (1 failed, 1 passed)
9 steps (1 failed, 4 skipped, 4 passed)
0m0.269s


テストが通るようにプロダクトコードを修正します。
(viewのラベルに日本語名を指定したり、ボタン名を日本語に変更)


そうすると、またテストが通るようになります。

.........

2 scenarios (2 passed)
9 steps (9 passed)
0m0.354s

あと

下記文言もそれぞれ日本語に変更可能ですが、
英語の方がしっくりきたのでそのままにしています。
Feature  フィーチャ
Scenario シナリオ
Given   前提
When   もし
And    かつ
Then   ならば