RSpecの話を少し
- 2011.08.16
 - Ruby/Rails, テスト, 勉強会資料
 
弊社内のRubyを使ったプロジェクトでは、自動テストツールのひとつとしてRSpec(バージョン2と1両方)を使っています。
 先日社内勉強会でRSpecのトピックを取り上げたので、いくつか紹介したいと思います。
Tipsなど
(以下のコードはバージョン1でも動作します)
as_null_object
通常、Mockオブジェクトに対してstub定義していないメソッドを呼ぶとMockExpectationErrorが発生します。ですがテストによっては、ある特定のメソッド呼び出し以外を無視したいケースもあるでしょう。(例えばログ出力内容の検証など)
 その場合はMockオブジェクトに対してas_null_objectメソッドを呼んでおくことで、特定のメソッド呼び出し以外を無視することができます。
def start(logger)
  logger.debug('debug message.')  # as_null_objectによりinfo呼び出し以外は無視される
  logger.info('start')
  # ...
end
it '処理開始がログに記録されること' do
  logger = mock('logger').as_null_object
  logger.should_receive(:info).with('start')
  start(logger)
end</code>
</pre>
参考
Shared Examples
例えばダックタイピングを適用して、同じように振る舞うクラスを複数実装したとしましょう。その共通する振る舞いのテストコードを別々に用意すると、重複が発生してしまいますし保守が面倒です。
 RSpecではそういった共通するexample群に名前を付けて、example group間で共有することができます。
shared_examples_for "Feedforce社員" do
  it "なぜか社長の誕生日を覚えていること" do
    should ...
  end
end
describe 'エンジニア' do
  subject { Engineer.new }
  it_should_behave_like 'Feedforce社員'
end
describe 'ディレクタ' do
  subject { Director.new }
  it_should_behave_like 'Feedforce社員'
end
参考
マッチャいろいろ
「should ==」ばかり使ってテストを書くこともできますが、適したマッチャを使うことでテストの内容がより明確になり、分かりやすい失敗メッセージを得ることができます。
have(n)
columns = [1, 2, 3]
# これでも悪くはないですが...
columns.size.should == 4      # "expected: 4, got: 3 (using ==)"
# こちらの方が良い
columns.should have(4).items  # "expected 4 items, got 3"
be(*args)
result = 11
# これでも間違いではないですが...
(result < 10).should be_true  # "expected false to be true"
# こちらの方が良い
result.should be < 10         # "expected < 10, got 11"
ちなみにbeの実装はMatchers::Beのインスタンスを返してMatchers::Beは演算子"<"の実装をオーバーライドしてるからMatchers::BeComparedToのインスタンスを返して、それがshouldの引数になって...という具合です。
Stub chain
stub_chainメソッドを使うことで、あるオブジェクトの関連オブジェクトのさらに関連オブジェクト...のようにネストしたスタブを一度に定義できます。
 あまりにネストした関連は設計を改善した方が良さそうですが...。
# こう書いても良いが...
boss = mock('ボス', :policy => 'みんながんばれ')
member.stub!(:boss).and_return(boss)
member.boss.policy  # 'みんながんばれ'
# こう書ける
member.stub_chain(:boss, :policy).and_return('みんながんばれ')
member.boss.policy  # 'みんながんばれ'
参考
RSpec2の話
RSpec1からの主な変更点
- 実行コマンドがspecからrspecに
 Rakeタスクでのオプション指定方法が変更に
- spec_opts → rspec_opts など
 
コマンドラインオプションが .rspec ファイルで指定できるように
- ~/.rspec とプロジェクトのルートにあるものが参照される
 
参考
新機能いくつか
--profileオプション
- 時間がかかったテストを報告してくれる
 
Metadata
- 各exampleがメタデータを持つ
 - example内でexample.metadataとして参照可能
 - describe, itのオプションとして任意の値を設定可能
 - 実行対象の絞り込み等に使える
 
around(:each)
- トランザクションやファイルのオープン・クローズセットなど、なった前処理、後処理を書くのに使える。
 
describe "around hook" do
  around(:each) do |example|
    puts "around each before"
    example.run
    puts "around each after"
  end
  it "gets run in order" do
    puts "in the example"
  end
end
RSpec Rails2
have_tag マッチャが提供されなくなった
- webratのhave_tag(have_selector)を使う
 
require "nokogiri"
require "webrat/core/matchers"
RSpec.configure do |config|
  config.include(Webrat::Matchers)
end
it 'aタグの組み立てテスト' do
  tag = '...'
  tag.should have_selector('a', :href => '...', :content => 'text')
end
Rails2系では使えません
rspec-rails-2 supports rails-3.0.0 and later. For earlier versions of Rails, you need rspec-rails-1.3.
http://relishapp.com/rspec/rspec-rails
