2009/12/15

Cucumber

Cucumber は受け入れテストのためのツールで、Ruby on Rails 以外でも使えるみたい。

参考

インストール。

gem install cucumber
sudo apt-get install libxml2-dev libxslt1-dev
gem install nokogiri
gem install webrat

準備。アプリケーションのルートディレクトリで次を実行する。

script/generate cucumber

がんばって日本語でいってみようと思うので webrat_steps.rb を webrat_ja_steps.rb にコピーして(必要なところだけ)翻訳することにする。

xxx_steps.rb の中で操作は Webrat を使って行われるらしい。

日本語で実行する場合は cucumber -l ja と実行する。

「もし」や「かつ」は cucumber-0.4.4/lib/cucumber/languages.yml にあらかじめ定義されている。

Post のテストを作ってみる。

$ script/generate feature post
exists features/step_definitions
create features/manage_posts.feature
create features/step_definitions/post_steps.rb
各ファイルの使い方は↓な感じか。
  • manage_posts.feature にテストというよりむしろ仕様を書く。
  • post_steps.rd にポスト固有の手順(前提、もし、ならばの中身)を書く。ポスト固有でないものは webrat_ja_steps.rd に書く。

features/step_definitions/webrat_ja_steps.rb

# -*- coding: utf-8 -*-
# IMPORTANT: This file was generated by Cucumber 0.4.4
# Edit at your own peril - it's recommended to regenerate this file
# in the future when you upgrade to a newer version of Cucumber.
# Consider adding your own code to a new file instead of editing this one.

require File.expand_path(File.join(File.dirname(__FILE__), "..", "support", "paths"))

# Commonly used webrat steps
# http://github.com/brynary/webrat

#Given /^(?:|I )am on (.+)$/ do |page_name|
Given /"([^\"]*)" ?[にへ]アクセス/ do |page_name|
visit path_to(page_name)
end

#When /^(?:|I )go to (.+)$/ do |page_name|
# visit path_to(page_name)
#end

When /"([^\"]*)" ?ボタンをクリック/ do |button|
click_button(button)
end

When /"([^\"]*)" ?リンクをクリック/ do |link|
click_link(link)
end

#When /^(?:|I )follow "([^\"]*)" within "([^\"]*)"$/ do |link, parent|
When /"([^\"]*)" ?の ?"([^\"]*)" ?リンクをクリック/ do |parent, link| # "
click_link_within(parent, link)
end

#When /^(?:|I )fill in "([^\"]*)" with "([^\"]*)"$/ do |field, value|
When /"([^\"]*)" ?に ?"([^\"]*)" ?[とを]入力$/ do |field, value| # "
fill_in(field, :with => value)
end

#When /^(?:|I )fill in "([^\"]*)" for "([^\"]*)"$/ do |value, field|
# fill_in(field, :with => value)
#end

# Use this to fill in an entire form with data from a table. Example:
#
# When I fill in the following:
# | Account Number | 5002 |
# | Expiry date | 2009-11-01 |
# | Note | Nice guy |
# | Wants Email? | |
#
# TODO: Add support for checkbox, select og option
# based on naming conventions.
#
When /^(?:|I )fill in the following:$/ do |fields|
fields.rows_hash.each do |name, value|
When %{I fill in "#{name}" with "#{value}"}
end
end

When /^(?:|I )select "([^\"]*)" from "([^\"]*)"$/ do |value, field|
select(value, :from => field)
end

# Use this step in conjunction with Rail's datetime_select helper. For example:
# When I select "December 25, 2008 10:00" as the date and time
When /^(?:|I )select "([^\"]*)" as the date and time$/ do |time|
select_datetime(time)
end

# Use this step when using multiple datetime_select helpers on a page or
# you want to specify which datetime to select. Given the following view:
# <%= f.label :preferred %><br />
# <%= f.datetime_select :preferred %>
# <%= f.label :alternative %><br />
# <%= f.datetime_select :alternative %>
# The following steps would fill out the form:
# When I select "November 23, 2004 11:20" as the "Preferred" date and time
# And I select "November 25, 2004 10:30" as the "Alternative" date and time
When /^(?:|I )select "([^\"]*)" as the "([^\"]*)" date and time$/ do |datetime, datetime_label|
select_datetime(datetime, :from => datetime_label)
end

# Use this step in conjunction with Rail's time_select helper. For example:
# When I select "2:20PM" as the time
# Note: Rail's default time helper provides 24-hour time-- not 12 hour time. Webrat
# will convert the 2:20PM to 14:20 and then select it.
When /^(?:|I )select "([^\"]*)" as the time$/ do |time|
select_time(time)
end

# Use this step when using multiple time_select helpers on a page or you want to
# specify the name of the time on the form. For example:
# When I select "7:30AM" as the "Gym" time
When /^(?:|I )select "([^\"]*)" as the "([^\"]*)" time$/ do |time, time_label|
select_time(time, :from => time_label)
end

# Use this step in conjunction with Rail's date_select helper. For example:
# When I select "February 20, 1981" as the date
When /^(?:|I )select "([^\"]*)" as the date$/ do |date|
select_date(date)
end

# Use this step when using multiple date_select helpers on one page or
# you want to specify the name of the date on the form. For example:
# When I select "April 26, 1982" as the "Date of Birth" date
When /^(?:|I )select "([^\"]*)" as the "([^\"]*)" date$/ do |date, date_label|
select_date(date, :from => date_label)
end

When /^(?:|I )check "([^\"]*)"$/ do |field|
check(field)
end

When /^(?:|I )uncheck "([^\"]*)"$/ do |field|
uncheck(field)
end

When /^(?:|I )choose "([^\"]*)"$/ do |field|
choose(field)
end

When /^(?:|I )attach the file at "([^\"]*)" to "([^\"]*)"$/ do |path, field|
attach_file(field, path)
end

Then /^タイトルが ?"([^\"]*)"/ do |text| # "
response.should have_tag('title', text)
end

Then /^説明が ?"([^\"]*)"/ do |text| # "
response.should have_tag("meta[name=description][content=?]", text)
end

Then /^キーワードが ?"([^\"]*)"/ do |text| # "
response.should have_tag("meta[name=keywords][content=?]", text)
end

#Then /^(?:|I )should see "([^\"]*)"$/ do |text|
Then /^"([^\"]*)" ?[とがは]表示される/ do |text|
response.should contain(text)
end

#Then /^(?:|I )should see "([^\"]*)" within "([^\"]*)"$/ do |text, selector|
Then /^"([^\"]*)" ?に ?"([^\"]*)" ?[とがは]表示される/ do |selector, text| # "
within(selector) do |content|
content.should contain(text)
end
end

#Then /^(?:|I )should see \/([^\/]*)\/$/ do |regexp|
Then /^\/([^\/]*)\/ ?[とがは]表示される/ do |regexp|
regexp = Regexp.new(regexp)
response.should contain(regexp)
end

#Then /^(?:|I )should see \/([^\/]*)\/ within "([^\"]*)"$/ do |regexp, selector|
Then /^"([^\"]*)" ?に ?\/([^\/]*)\/ ?[とがは]表示される/ do |selector, regexp|
within(selector) do |content|
regexp = Regexp.new(regexp)
content.should contain(regexp)
end
end

Then /^"([^\"]*)" ?[とがは]表示されない/ do |text|
response.should_not contain(text)
end

Then /^(?:|I )should not see "([^\"]*)" within "([^\"]*)"$/ do |text, selector|
within(selector) do |content|
content.should_not contain(text)
end
end

Then /^(?:|I )should not see \/([^\/]*)\/$/ do |regexp|
regexp = Regexp.new(regexp)
response.should_not contain(regexp)
end

Then /^(?:|I )should not see \/([^\/]*)\/ within "([^\"]*)"$/ do |regexp, selector|
within(selector) do |content|
regexp = Regexp.new(regexp)
content.should_not contain(regexp)
end
end

#Then /^the "([^\"]*)" field should contain "([^\"]*)"$/ do |field, value|
Then /"([^\"]*)" ?に ? "([^\"]*)" ?と入力され/ do |field, value|
field_labeled(field).value.should =~ /#{value}/
end

Then /^the "([^\"]*)" field should not contain "([^\"]*)"$/ do |field, value|
field_labeled(field).value.should_not =~ /#{value}/
end

Then /^the "([^\"]*)" checkbox should be checked$/ do |label|
field_labeled(label).should be_checked
end

Then /^the "([^\"]*)" checkbox should not be checked$/ do |label|
field_labeled(label).should_not be_checked
end

Then /^(?:|I )should be on (.+)$/ do |page_name|
URI.parse(current_url).path.should == path_to(page_name)
end

Then /^show me the page$/ do
save_and_open_page
end

features/step_definitions/post_steps.rb

# -*- coding: utf-8 -*-
#Given /^the following posts:$/ do |posts|
Given /^次のポストがある$/ do |posts|
Post.create!(posts.hashes)
end

#When /^I delete the (\d+)(?:st|nd|rd|th) post$/ do |pos|
When /^(\d+) ?番目のポストを削除/ do |pos|
visit posts_url
within("table > tr:nth-child(#{pos.to_i+1})") do
click_link "削除"
end
end

When /^(\d+) ?番目のポストを編集/ do |pos|
visit posts_url
within("table > tr:nth-child(#{pos.to_i+1})") do
click_link "編集"
end
end

#Then /^I should see the following posts:$/ do |expected_posts_table|
Then /^次のポストが表示されること$/ do |expected_posts_table|
expected_posts_table.diff!(table_at('table').to_a)
end

features/manage_posts.feature

フィーチャ: ポスト
In order to [goal]
[stakeholder]
wants [behaviour]

シナリオ: ポストの一覧
前提 次のポストがある
| name | title | content |
| 山田太郎 | お昼ごはん | ○○麺所に行った。 |
| 田中京子 | 散歩のこと | 猫を追跡した。 |
| 佐藤悟 | 休日のこと | いこーよ |
もし "ポスト一覧" へアクセスした
ならば "山田太郎" と表示されること
かつ "お昼ごはん" と表示されること
かつ "○○麺所に行った。" と表示されること
かつ "田中京子" と表示されること
かつ "散歩のこと" と表示されること
かつ "猫を追跡した。" と表示されること

シナリオ: ポストの削除
前提 次のポストがある
| name | title | content |
| 山田太郎 | お昼ごはん | ○○麺所に行った。 |
| 田中京子 | 散歩のこと | 猫を追跡した。 |
| 佐藤悟 | 休日のこと | いこーよ |
もし 2番目のポストを削除した
ならば "田中京子" は表示されない
# undefined method `css_search' for Webrat::XML:Module (NoMethodError) と
# しかられる。
# http://groups.google.com/group/cukes/browse_thread/thread/81b59d2785dc4256#
# ならば 次のポストが表示されること
# | name | title | content |
# | 山田太郎 | お昼ごはん | ○○麺所に行った。 |
# | 佐藤悟 | 休日のこと | いこーよ |


シナリオ: ポストの編集
前提 次のポストがある
| name | title | content |
| 山田太郎 | お昼ごはん | ○○麺所に行った。 |
| 田中京子 | 散歩のこと | 猫を追跡した。 |
| 佐藤悟 | 休日のこと | いこーよ |
もし "ポスト一覧" へアクセスし
かつ 2番目のポストを編集する
ならば "名前""田中京子" と入力されている
かつ "タイトル""散歩のこと" と入力されている
かつ "内容""猫を追跡した。" と入力されている
もし "名前""田中今日子" と入力
かつ "タイトル""散歩日和でした" と入力
かつ "内容""猫を追跡したが、ふりきられた。" と入力
かつ "保存" ボタンをクリック
ならば "田中今日子" と表示されること
かつ "散歩日和でした" と表示されること
かつ "猫を追跡したが、ふりきられた。" と表示されること

実行してみる(実際は赤と緑の色がついている)。

$ cucumber -l ja features/manage_posts.feature
フィーチャ: ポスト
In order to [goal]
[stakeholder]
wants [behaviour]

シナリオ: ポストの一覧 # features/manage_posts.feature:6
前提 次のポストがある # features/step_definitions/post_steps.rb:3
| name | title | content |
| 山田太郎 | お昼ごはん | ○○麺所に行った。 |
| 田中京子 | 散歩のこと | 猫を追跡した。 |
| 佐藤悟 | 休日のこと | いこーよ |
もし "ポスト一覧" へアクセスした # features/step_definitions/webrat_ja_steps.rb:13
ならば "山田太郎" と表示されること # features/step_definitions/webrat_ja_steps.rb:140
かつ "お昼ごはん" と表示されること # features/step_definitions/webrat_ja_steps.rb:140
かつ "○○麺所に行った。" と表示されること # features/step_definitions/webrat_ja_steps.rb:140
かつ "田中京子" と表示されること # features/step_definitions/webrat_ja_steps.rb:140
かつ "散歩のこと" と表示されること # features/step_definitions/webrat_ja_steps.rb:140
かつ "猫を追跡した。" と表示されること # features/step_definitions/webrat_ja_steps.rb:140

シナリオ: ポストの削除 # features/manage_posts.feature:20
前提 次のポストがある # features/step_definitions/post_steps.rb:3
| name | title | content |
| 山田太郎 | お昼ごはん | ○○麺所に行った。 |
| 田中京子 | 散歩のこと | 猫を追跡した。 |
| 佐藤悟 | 休日のこと | いこーよ |
もし 2番目のポストを削除した # features/step_definitions/post_steps.rb:8
ならば "田中京子" は表示されない # features/step_definitions/webrat_ja_steps.rb:165

# undefined method `css_search' for Webrat::XML:Module (NoMethodError) と
# しかられる。
# http://groups.google.com/group/cukes/browse_thread/thread/81b59d2785dc4256#
# ならば 次のポストが表示されること
# | name | title | content |
# | 山田太郎 | お昼ごはん | ○○麺所に行った。 |
# | 佐藤悟 | 休日のこと | いこーよ |
シナリオ: ポストの編集 # features/manage_posts.feature:37
前提 次のポストがある # features/step_definitions/post_steps.rb:3
| name | title | content |
| 山田太郎 | お昼ごはん | ○○麺所に行った。 |
| 田中京子 | 散歩のこと | 猫を追跡した。 |
| 佐藤悟 | 休日のこと | いこーよ |
もし "ポスト一覧" へアクセスし # features/step_definitions/webrat_ja_steps.rb:13
かつ 2番目のポストを編集する # features/step_definitions/post_steps.rb:15
ならば "名前" に "田中京子" と入力されている # features/step_definitions/webrat_ja_steps.rb:188
かつ "タイトル" に "散歩のこと" と入力されている # features/step_definitions/webrat_ja_steps.rb:188
かつ "内容" に "猫を追跡した。" と入力されている # features/step_definitions/webrat_ja_steps.rb:188
もし "名前" に "田中今日子" と入力 # features/step_definitions/webrat_ja_steps.rb:35
かつ "タイトル" に "散歩日和でした" と入力 # features/step_definitions/webrat_ja_steps.rb:35
かつ "内容" に "猫を追跡したが、ふりきられた。" と入力 # features/step_definitions/webrat_ja_steps.rb:35
かつ "保存" ボタンをクリック # features/step_definitions/webrat_ja_steps.rb:21
ならば "田中今日子" と表示されること # features/step_definitions/webrat_ja_steps.rb:140
かつ "散歩日和でした" と表示されること # features/step_definitions/webrat_ja_steps.rb:140
かつ "猫を追跡したが、ふりきられた。" と表示されること # features/step_definitions/webrat_ja_steps.rb:140

3 scenarios (3 passed)
24 steps (24 passed)
0m0.607s

expected_posts_table.diff! を使うと次エラーが発生するのは http://groups.google.com/group/cukes/browse_thread/thread/81b59d2785dc4256# に書かれている現象だと思うけど、解決はしてない?

      undefined method `css_search' for Webrat::XML:Module (NoMethodError)

日本語でいくために無駄にがんばっている気もするが、あとは画面のハードコピーでもあればテスト仕様書とテスト結果としての体裁がそろってテストドキュメント一式完成となるなら、がんばる価値はあると思う。テストドキュメントが要求されない場合は。。。おおわくでしっかりしたテストがあればソースいじりやすいから。ね。

0 件のコメント: