RSpec で TDiary::Plugin のテストを書く
最近の tDiary はテストが充実している。テストのベースは Capybara を使った外部テストが主だけど、今後のリファクタリング・機能追加のために内部テストを書いてみる。
テスト対象として選んだのは TDiary::Plugin 。選定理由はプラグイン作者になじみが深いことと、外部環境にあまり依存しないクラスなのでテストが書きやすそうなのが理由。
テストの実行方法
rake コマンドですべての spec を実行できるけど、時間がかかりすぎるので開発時は対象のspecだけを個別に実行する。
$ bundle exec rspec --colour -fd spec/core/plugin_spec.rb
テストの書き方
Rubyist Magazine - 改めて学ぶ RSpecを参考に、describe クラス名ーdescribe メソッド名ーcontext 状態(外部環境)ーit テストケースの順に書いていく。
describe TDiary::Plugin do
before do
config = PluginFake::Config.new
config.plugin_path = 'spec/fixtures/plugin'
@plugin = TDiary::Plugin.new({ :conf => config, :debug => true })
end
describe '#load_plugin' do
before { @plugin.load_plugin('spec/fixtures/plugin/sample.rb') }
subject { @plugin }
it '読み込まれたプラグインのメソッドを呼び出せること' do
subject.sample.should eq 'sample plugin'
end
context 'リソースファイルが存在する場合' do
before do
@plugin.instance_variable_get(:@conf).lang = 'ja'
@plugin.load_plugin('spec/fixtures/plugin/sample.rb')
end
it 'Confファイルで指定した言語に対応するリソースが読み込まれること' do
@plugin.sample_ja.should eq 'サンプルプラグイン'
end
end
end
end
テストの書き方は テスト対象.should eq '期待する結果'
が基本形。 RSpec はソースを自然に読めるように subject や Matcher が豊富で混乱するけど、考えすぎずにベタに書いていくのがよさそう。最初は状態によって context を分けるくらいでいい。
これを-fdオプション付きで実行すると、いい感じにテストケースの一覧を表示してくれる。
$ bundle exec rspec --colour -f d spec/core/plugin_spec.rb
TDiary::Plugin
#load_plugin
読み込まれたプラグインのメソッドを呼び出せること
リソースファイルが存在する場合
Confファイルで指定した言語に対応するリソースが読み込まれること
Finished in 0.08684 seconds
34 examples, 0 failures
スタブとモック
RSpecの題材としては、よくStackのように内部に状態を持つような例が取り上げられている。でも実際には、外部のWebAPIやデータベースからデータを取得するような、他のオブジェクトと連携していることが多い。テストのたびに外部のWebやDBやへアクセスするのは大変なので、代わりにスタブを使うと良いことが分かった。RSpecでは double メソッドを使ってスタブやモックを生成できる。
詳しくは RSpecのプレゼンが参考になる(Mocks and Stubsのところ)。スタブとモックの違いは混乱するけど、DBなどの代わりに単純に値を返すのがスタブ。一方、テスト対象のオブジェクトからの呼び出されかた・順序までを確認するのがモックらしい。
tDiaryのプラグインはコールバックを登録できるようになっている。コールバックが想定通りに呼ばれたかどうかを確認したいときには、スタブでなくモックを使う。この例では、コールバックとして登録する proc1, proc2 がモックに相当する。 should_receiveやwithで呼ばれるべきメソッドや引数を指定できる。
describe '#title_proc' do
let (:proc1) { lambda {|date, title| "title1" } }
let (:proc2) { lambda {|date, title| "title2" } }
let (:date) { Time.local(2012, 1, 2) }
before do
@plugin.__send__(:add_title_proc, proc1)
@plugin.__send__(:add_title_proc, proc2)
end
it '前のprocの結果が次のprocに渡されること' do
proc1.should_receive(:call).with(date, 'title').and_return('title1')
proc2.should_receive(:call).with(date, 'title1')
@plugin.__send__(:title_proc, date, 'title')
end
end
rspecではスタブとモックの機能的な違いはあまりない。使い方によってスタブにもモックにもなれる。最初はあまり気にせずに、書きながら慣れていくしかなさそう。
テストケースを書いていくと、自然とテストのしやすさに意識が向く。そして、テストのしやすさは、モジュール・オブジェクトの独立性に関わっているんだなと気付く。この感覚は、実際に書いてみないと分からないかも。