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