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が思ったより再利用できそうだし悪くないかもしれない。
    とりあえずしばらく書いてみるかな?

    しかしユースケースの書き方とかかなり忘れてるな。