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).

この手のデータ構造で必要になるモノとしてはだいたい押さえてる感じですね。

おまけ: tableish

Wikiにもあるように、特に検証のための表は、じっさいはHTMLのテーブルと比較するようとが多いと思います。
そのため、cucumber-rails(パッケージが分割されてます。Webratとかはこちら)はtableishという便利なメソッドもあります。これはHTMLの表やリストなど、表/一覧ぽいデータから、Cucumber::Ast::Table#diff! で比較できるようなデータ構造を抜いてきてくれるメソッドです。
いろんな使い方がありますので、くわしくはWikiを見てくださいませ。

次回予告

シナリオアウトラインについてもあとで書きます。