at posts/single.html

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ではスタブとモックの機能的な違いはあまりない。使い方によってスタブにもモックにもなれる。最初はあまり気にせずに、書きながら慣れていくしかなさそう。

テストケースを書いていくと、自然とテストのしやすさに意識が向く。そして、テストのしやすさは、モジュール・オブジェクトの独立性に関わっているんだなと気付く。この感覚は、実際に書いてみないと分からないかも。

関連する日記