Cucumberがアツい
仕事で作っているRailsアプリにCucumberを突っ込んでみました。これは熱い。いやもう十分、お客さんに見せて分かってもらえる気がします。たぶん。もちろん準備は必要だし、受け入れ仕様をすべてお客さんに書いてもらうというのは難しいですけど*1。
とりあえず導入はこちらから。最近はNokogiriが必要です。あとTerminal.appで--no-colorつけずに実行するとTerminal.appがひどいことになるのでiTermお薦めです。
http://github.com/aslakhellesoy/cucumber/wikis/ruby-on-rails
使い方
rails部門のtophatenarらしいので(あんまり関係ないけど)Rails前提の話しです。
cucumberプラグインをインストールして、script/generate cucumberすると、Railsをテストするためのいろんなファイルが生成されます。たとえばscript/cucumberに実行スクリプトが入ります。
差しあたり重要なのは以下の二つほど。
- features/support/env.rb
- cucumber全体の設定を各ファイルです。最初は編集しなくていいでしょう。
- features/step_definitions/webrat_steps.rb
- webratという、Webアプリのテスト用ツールを使うためのコードです。これも編集しなくていいはず。
cucumberの構造
cucumberでは、まずfeatures/hoge.featureという命名規則でfeatureファイルを書きます。これが「自然言語(ぽい)」「プレーンテキスト」で「受け入れ仕様を記述する」ファイルとなります。
たとえば次のようなコマンドで受け入れ仕様のひな形を作ります。
$ ruby script/generate feature Entry exists features/step_definitions create features/manage_entries.feature create features/step_definitions/entry_steps.rb
ここでできたfeatures/manage_entries.featureがfeatureファイルですね。中身はこんなの。
$ cat features/manage_entries.feature Feature: Manage entries In order to keep track of entries A entry mechanic Should be able to manage several entries Scenario: Register new entry Given I am on the new entry page And I press "Create" Scenario: Delete entry Given there are 4 entries When I delete the first entry Then there should be 3 entries left More Examples: | initial | after | | 100 | 99 | | 1 | 0 |
どうみてもプレーンテキストですね。すばらしい。これを実行できるのがCucumberです。
で、どうみてもプレーンテキストなので、このままでは実行できません。これをなんとかして実行するための仕組みがstepsと呼ばれているものです。具体的には features/step_definitions/entry_steps.rb ですね。
$ cat features/step_definitions/entry_steps.rb Given /I am on the new entry page/ do visits "/entries/new" end Given /there are (\d+) entries/ do |n| Entry.transaction do Entry.destroy_all n.to_i.times do |n| Entry.create! :name => "Entry #{n}" end end end When /I delete the first entry/ do visits entries_url clicks_link "Destroy" end Then /there should be (?d+) entries left/ do |n| Entry.count.should == n.to_i response.should have_tag("table tr", n.to_i + 1) # There is a header row too end
ということでfeatureファイルの文言にマッチするような正規表現と、実際のRubyコードが並んでいます。具体的にはインデントレベル2くらいのGivenやAnd、When、Thenの文言に対応しています。
Cucumberは実行時にfeatureファイルを解析し、文言がマッチするstepの処理を実行してテストします。stepは正規表現を使えますので、文章中の一部を動的に取り出すことも出来ます。たとえば "Given there are 4 entries"というfeature内の文言に対し、Given /there are (\d+) entries/ というstepがマッチして、"4"を動的に指定できているイメージが伝わるはず。
で、Given、When、Thenはそれぞれ、前提、処理内容、その結果(に対する検証)、となります。これを上から順に書いていくわけですね。Andは前のstepと同じ事を繰り返します。Thenでは検証をするんですが、検証にはRSpecの語彙(should/should_not)が使えます。
また、このシナリオを日本語で書いてみればこんな感じです。
Scenario: 新しいエントリの登録 前提 I am on the new entry page かつ I press "Create" Scenario: エントリの削除 前提 there are 4 entries もし I delete the first entry ならば there should be 3 entries left
さらに、"there are 4 entries"とかのところはstepで正規表現で指定してます。そのためstepに次のような定義があれば
Given /^(\d+).? のエントリがある/ do |n| Entry.transaction do Entry.destroy_all n.to_i.times do |n| Entry.create! :name => "Entry #{n}" end end
こう書けます。
Scenario: エントリの削除 前提 4つ のエントリがある もし I delete the first entry ならば there should be 3 entries left
こんな感じでstepを充実させていけばOK。
と、ここで終わると面倒なだけじゃんと思うかもしれませんが、意外とテストに書く語彙(=やること)は決まってるじゃないですか。こうExcelでテスト仕様書書いてても。なので、一回つくってしまえば意外と使い回せそうな気がしてます。
で、実はここで書いた「前提」とかは角谷さんの尽力によりそのまま使えます。
http://github.com/aslakhellesoy/cucumber/commit/9291e7c8d1de8189dfda3d2d7318277731fb9c5b
ということでもう少し詳しい使い方はあとで書く。
Railsとの絡み
箇条書きで。
- Railsのテストをするためにwebratを使ってる
- webratでHTMLを検証するためにNokogiri使ってる
- RailsのテストはIntegrationTestのレイヤで。ActionController::Integration::Sessionを使ってます。
- ようはActionController::Dispatcher.dispach から先を検証している
- これをwebratのSessionでくるんで使う感じ。
- IntegrationTestの制約は当然受ける
- fixturesも使えるはず
OpenIDの件はあとで書く、ということで私のTwitterをみてwktkしてた人にはひどいことをしましたよね。ごめんなさい。あとで書きます。
*1:そもそも*すべて*お客さんに書いてもらうのをあんまり目指していない。ちゃんとシステムをみながら会話する語彙としては使えそう