Cucumberと表について(1)
いま参加しているプロジェクトで、ひさしぶりにCukeをゴリゴリと書いています。その際、チームメンバーから「表の使い方を教えて欲しい」という要望がありましたので、勉強会用にまとめます。
「表」とは?
まずはここをみてください。
http://wiki.github.com/aslakhellesoy/cucumber/multiline-step-arguments
CukeではAAで表が書けます。こんな感じのフィーチャを記述します。
# language: ja フィーチャ: 表のデモ TopHatenar陥落した私としては 表の機能を解説して いっぱいブクマされたい シナリオ: 表を、テーブルとして使う 前提 以下の表をテーブルにする: | title | author | | きゅうりかわいい| もろはし | | cool cuke! | morohashi | ならば "2"件のレコードがあること かつ "1"件目は"きゅうりかわいい"と"もろはし"であること
これは普通のデータセットとして使う方法と、シナリオアウトラインに流し込むデータに使うという、2種類の使い方があります。このエントリでは前者を解説します。
データセットアップに使う
まず、このAA表は、普通のテーブルっぽいオブジェクトとして使えます。使い方というわけではありませんが、表を使うためのstepでは最後に":"を入れる慣習があるようです。
Cukeは対応するstepがない状態で走らせると、stepの例を出力してくれます。今回は次のようになります。
前提 /^以下の表をテーブルにする:$/ do |table| # table is a Cucumber::Ast::Table pending # express the regexp above with the code you wish you had end ならば /^"([^\"]*)"件のレコードがあること$/ do |arg1| pending # express the regexp above with the code you wish you had end かつ /^"([^\"]*)"件目は"([^\"]*)"と"([^\"]*)"であること$/ do |arg1, arg2, arg3| pending # express the regexp above with the code you wish you had end
すぐ下にAA表を書いている最初のステップのひな形にtableというブロック引数があることに注目してください。このtableがCukeの表オブジェクト(Cucumber::Ast::Table)になります。
今回のように、Given(前提)で書くのであれば、表からデータを取得して使いたい場合がほとんどでしょう。この場合、Cucumber::Ast::Table.hashesで中のオブジェクトが簡単に取り出せます。
前提 /^以下の表をテーブルにする:$/ do |table| p table.hashes # => [{"author"=>"もろはし", "title"=>"きゅうりかわいい"}, {"author"=>"morohashi", "title"=>"cool cuke!"}] ... end
このように、一番上の行をヘッダ行と解釈したうえで、それぞれに{行の名前1 => 中身1, 行の名前2 => 中身2}というHashの配列がとれます。あとはこれをいい感じに料理すればよいでしょう。みんな大好きRubyなので、うまい具合にやってください。
たとえば、stepをこんな感じに仕立てればちゃんと通ります。いったんPlain Old Ruby Objectに落とせば、ほとんど悩まなくて済みますね。
Given /^以下の表をテーブルにする:$/ do |table| @records = table.hashes.map do |row| [row["title"], row["author"]] end end Then /^"([^\"]*)"件のレコードがあること$/ do |num| @records.should have(num.to_i).items end Then /^"([^\"]*)"件目は"([^\"]*)"と"([^\"]*)"であること$/ do |nth, col1, col2| @records[nth.to_i - 1].should == [col1, col2] end
データの検証に使う
セットアップに使うのもいいんですが、逆にテーブルを使ってデータの検証を行うことも出来ます。
シナリオ: 表を、データの検証に使う 前提 何か、データのストレージがある かつ "きゅうりかわいい"と"もろはし"というエントリを追加する かつ "cool cuke!"と"morohashi"というエントリを追加する ならば 以下のデータが容易されていること: | きゅうりかわいい| もろはし | | cool cuke! | morohashi |
この例は先ほどと逆に、Givenで配列の配列となるデータを作り、Thenでその配列の配列の状態をテーブルで表現しようとしています。これを実行すると、次のようなstepのひな形が出力されます。
前提 /^何か、データのストレージがある$/ do pending # express the regexp above with the code you wish you had end かつ /^"([^\"]*)"と"([^\"]*)"というエントリを追加する$/ do |arg1, arg2| pending # express the regexp above with the code you wish you had end ならば /^以下のデータが用意されていること:$/ do |table| # table is a Cucumber::Ast::Table pending # express the regexp above with the code you wish you had end
これを以下のように書き換えます。ポイントはCucumber::Ast::Table#diff!です。
Given /^何か、データのストレージがある$/ do @records = [] end Given /^"([^\"]*)"と"([^\"]*)"というエントリを追加する$/ do |arg1, arg2| @records << [arg1, arg2] end Then /^以下のデータが用意されていること:$/ do |table| table.diff!(@records) end
Cucumber::Ast::Table#diff!(tableish)は、引数tableishをいい感じに解釈して、自身Cucumber::Ast::Tableと比較してくれます。今回の例では、「ならば」で書いていたテーブルと、@recordsつまり配列の配列を比較してくれました。
もちろん、たとえば入力データが間違っている場合などは失敗します。
シナリオ: 表を、データの検証に使う 前提 何か、データのストレージがある かつ "胡瓜かわいい"と"もろはし"というエントリを追加する かつ "cool cuke!"と"morohashi"というエントリを追加する ならば 以下のデータが用意されていること: | きゅうりかわいい| もろはし | | cool cuke! | morohashi |
ならば 以下のデータが用意されていること: # features/step_definitions/foo_steps.rb:23 | きゅうりかわいい | もろはし | 胡瓜かわいい | | cool cuke! | morohashi | cool cuke! | Tables were not identical (Cucumber::Ast::Table::Different)
ちなみにdiff!で比較できる内容は、最初に紹介したWikiにも書いてあります。
- Another CucumberAst::Table
- An Array containing Arrays with Strings
- An Array containing Hashes with String => String (similar to the structure returned by CucumberAst::Table#hashes).
この手のデータ構造で必要になるモノとしてはだいたい押さえてる感じですね。
次回予告
シナリオアウトラインについてもあとで書きます。