[Ruby]Rspec::Storyの使い方

Rspec単体テストだけでなく、機能テストも出来る。
最近「ユースケース実践ガイド―効果的なユースケースの書き方 (OOP Foundations)」を読んでいて、もしやこれバイブル?と思っているのだが、この本で言う「ユースケース」をStoryと捉えて自動テストできるようにしたフレームワークが、Rspec::Storyではないかと思う。

ユースケース実践ガイド―効果的なユースケースの書き方 (OOP Foundations)」では、ユースケースはシナリオの集合体であると定義している。Rspec::Storyでも、Storyは複数のscenarioから成り立つ。ということは、まず主scenarioを書いて、それから代替scenarioを書いていけば良いわけだ。

ユースケース実践ガイド―効果的なユースケースの書き方 (OOP Foundations)

ユースケース実践ガイド―効果的なユースケースの書き方 (OOP Foundations)


で、実際のRspec::Storyの使い方は に詳しいのだが、Railsのことしか書いていないので、普通のRubyアプリで機能テストしようとするとなかなか苦労する・・というわけで、苦労した結果をまとめておく。

1.ストーリーファイルを書く

これは と同じ。
David ChelmskyのBlog(David Chelimsky » Blog Archive » Story Runner in Plain English)に、プレーンテキストで書けるようにしたよ!Wao!的な文言があって微笑ましい。

#
#ファイル名:summary
#
Story: 生徒の成績一覧ファイルを、集計コマンド(summary.rb)を使用して集計する
  アクター : 高校教師
  目的 : 成績一覧CSVファイルを元に、年度別の平均点、偏差値を集計する。
  結果 : 集計結果CSVファイルを得る。
  
  Scenario: 平成17年度の成績を集計する
    Given   tempディレクトリに移動している
    And     平成17年度成績一覧.csv がテストディレクトリに存在する
    When    ユーザーが 平成17年度成績一覧.csv を引数として、summary.rbコマンドを実行する
    Then    tempディレクトリを削除している
Note:
  • Blog では「Act as 役割、I want したいこと、So that こうなる」になっているが、変えても大丈夫かな?Rspecとしてどこかで文言を意識しているのかも。。

2.Stepを書く

ここは と結構違う。こんな感じ。

#
#ファイル名:summary_step.rb
#
class SummarySteps < Spec::Story::StepGroup
  steps do |define|
    define.given("tempディレクトリに移動している") do
      @pwd = Dir.pwd
      @test_dir = File.expand_path(File.dirname(__FILE__))
      @temp_dir = @test_dir + '/temp'
      Dir::mkdir(@temp_dir)
    end
    
    define.given("$file_name がテストディレクトリに存在する") do |file_name|
      FileUtils.cp(File.join(@test_dir, file_name), @temp_dir)
    end
    
    define.then("tempディレクトリを削除している") do
      Dir::chdir(@pwd)
      FileUtils.remove_entry(@temp_dir)
    end
  end
end

フィルタコマンド系のテストをするときは、tempディレクトリを作って素材ファイルを作って最後にtempディレクトリを削除する、という処理をしないといけないので、ここに素材として置いておく。

Note:
  • requireは不要。実際に必要なライブラリをrequireするのは、呼び出し元のsummary.rbが実施するので。
  • 「steps_for :ストーリー名 do; Given 〜 」という書き方ができるかどうか不明。。→少なくとも、http://blog.davidchelimsky.net/articles/2007/10/21/story-runner-in-plain-english では書き方が違う。(2007/10/21の記事なので古いだけかも)
  • 各stepでは、インスタンス変数は共有される。Spec::Story::StepGroupの子(SummaryStep)のインスタンス変数なのだから当然か・・。

3.呼び出しスクリプトを書く

requireをどうするかが問題だった。。

#
#ファイル名:summary.rb
#
require 'rubygems'
require 'spec/story'
require 'spec'

require File.join(File.dirname(__FILE__), '../lib/summary') # テスト対象が ~/lib/summary.rb の場合
require File.join(File.dirname(__FILE__), File.basename(__FILE__, ".rb") + '_steps')

# assumes the other story file is named the same as this file minus ".rb" 
runner = Spec::Story::Runner::PlainTextStoryRunner.new(File.expand_path(__FILE__).gsub(".rb",""))
runner.steps << SummarySteps.new
runner.run

4.テストする。

C:\summary\test>ruby -Ks summary.rb
Running 1 scenarios

Story: 生徒の成績一覧ファイルを、集計コマンド(summary.rb)を使用して集計する

  アクター : 高校教師
  目的 : 成績一覧CSVファイルを元に、年度別の平均点、偏差値を集計する。
  結果 : 集計結果CSVファイルを得る。


  Scenario: 平成17年度の成績を集計する

    Given tempディレクトリに移動している
    And 平成17年度成績一覧.csv がテストディレクトリに存在する

    When ユーザーが 平成17年度成績一覧.csv を引数として、summary.rbコマンドを実行する (PENDING)

    Then tempディレクトリを削除している

1 scenarios: 0 succeeded, 0 failed, 1 pending

Pending Steps:
1) 生徒の成績一覧ファイルを、集計コマンド(summary.rb)を使用して集計する (平成17年度の成績を集計する): ユーザーが 平成17年度成績一覧.csv を引数として、summary.rbコマンドを実行する

C:\summary\test>