RSpecのStoryの書き方に関するメモ
RSpecでストーリーテストの書き方を調べたので忘れないようにまとめておく。
手順
- StoryとScenarioを書く [プレーンテキスト]
- Stepを書く [Rubyコード]
テストコードを書く前にテキストで仕様を書く。
テストコードはその仕様にあわせて書いていく。
ファイル構造は以下の通り。
- stories/hoge/hoge – StoryとScenario
- stories/hoge/hoge.rb – Stepをまとめて実行するためのコード
- stories/hoge/hoge_steps.rb – Step
今のところ標準でこのひな形を作るジェネレータは用意されていないが、Storygenで生成できる。
多分これはそのうちマージされるのかな?
StoryとScenarioを書く
Storygenで生成されるひな形のStoryとScenarioは以下の通り。
Story: put your story name here
As role
I want feature
So that benefit
Scenario: everything is cool
Given precondition
And another precondition
When action
And another action
Then outcome
And another outcome
英語だとまったく素敵に自然に書けるっぽい。
だが私は母国語を愛している。というわけで以下。
Story: ショッピングカートで買い物をする
アクター:ユーザー
目的:ショッピングカートにものを入れて中身と合計価格が確認できる
結果:安心してお買い物を楽しめる
※ ここは自由記述っぽい As, I want, So thatをそのまま残すか悩んだけど消した。
Scenario: ショッピングカートにものを入れる
Given ショッピングカートには何も入っていない
And りんご は単価 100 円
And みかん は単価 80 円
When ユーザーは りんご を 3 個カートに入れる
And ユーザーは みかん を 5 個カートに入れる
And ユーザーはショッピングカートの内容を確認する
Then ショッピングカートの合計価格は 700 円
And ショッピングカートには りんご が 3個 入っている
And ショッピングカートには みかん が 5個 入っている
以下備考。
- Scenarioはこの後に続けて複数書いていける。
- Given, When, Thenは単純に上から順に実行される。
- Thenの後にまたWhenを書いてつなげていくのも可。
- 単語や数値だけ変えた同じ形の文はStepで再利用できる文になる。
- 単語や数値は半角スペースで他と区切る(英語だったら自然に単語で切れる)
- Scenarioが異なってもStepは再利用できる。
- Scenarioの文の内容を変えるとStepの方も変えなくてはならない。これは若干めんどい。
- Scenarioの量が増えるとStepの再利用率が高くなっていいかんじになる?
Stepを書く
Scenarioがまとまったらそれに沿ってStepを書いていく。
Givenには前提条件。
Whenにはアクション。
Thenには結果を書く。
これは紳士協定的決まりで、どの種類のStepに何を書かないとエラーになるとかそういうことはない。
Givenに前提条件の準備からアクションから結果の検証まで全部書いちゃうこともできるし、頭にいきなりThenと書いてその後ろに前提条件を書いたりもできるのだが、まあそういう変なことはしない方がいいだろう。
Given
前提条件。
商品データの準備。
一応ショッピングカートに何も入ってないことも確認。
# Precondition steps / Given
steps_for :hoge do
Given "ショッピングカートには何も入っていない" do
get "cart/show"
response.should have_tag('span[class="no_item_message"]')
response.should have_tag('span[class="amount"]', "0")
# 一応確認
end
Given "$name は単価 $price 円" do |name, price|
Item.create(:name => name, :price => price)
end
end
下の方のGivenは「りんご は単価 100 円」「みかん は単価 80 円」に適用される。
こういう仕組みによってStepが再利用できる。
When
アクション。
ユーザーの行動をトレース。
カートに商品を入れて中身を確認する。
# Action steps / When
steps_for :hoge do
When "ユーザーは $name を $n 個カートに入れる" do |name, count|
item = Item.find_by_name(name)
post "cart/add/", :id => item.id, :count => count
end
When "ユーザーはショッピングカートの内容を確認する" do
get "cart/show"
end
end
ここでは上のWhenが「ユーザーは りんご を 3 個カートに入れる」「ユーザーは みかん を 5 個カートに入れる」に適用される。
Then
結果。
HTMLの内容を確認する。
合計価格とカートの中身の明細。
# Outcome steps / Then
steps_for :hoge do
Then "ショッピングカートの合計価格は $amount 円" do |amount|
response.should be_success
response.should have_tag('span[class="amount"]', amount)
end
Then "ショッピングカートには $name が $n 個入っている" do |name, count|
item = Item.find_by_name(name)
response.should have_tag("span[class=\"item_name_#{item.id}\"]", name)
response.should have_tag("span[class=\"item_count_#{item.id}\"]", count)
end
end
これを書いているうちにひとつ技を思い出した。
HTMLに出力される変数の内容を確認しやすくするために、変数をユニークな名前のidかclassを持たせたspanタグで囲むとよいって技があったのだった。
ストーリーテストの実行
Stepがすべて書けたら以下のように実行。
$ ruby stories/hoge/hoge.rb
rakeのタスクでrake spec:storiesとか言うタスクがそのうちできるのでしょう。
今はないので欲しければ自分で定義するのもまたよしでせうか。
雑感
Scenarioの文の内容を変えるとStepの方も変えなくてはならない、という部分が若干めんどくさいけど、Stepが思ったより再利用できそうだし悪くないかもしれない。
とりあえずしばらく書いてみるかな?
しかしユースケースの書き方とかかなり忘れてるな。