RSpec 3の新機能であるComposable Matchersの使い方の例をtest-unitならどう書くか紹介します。リンク先のコードを示し、それのtest-unitバージョンを示す、という流れを繰り返します。
テスト対象は次のコードです。
1 2 3 4 5 6 7 8 9 10 11 |
class BackgroundWorker attr_reader :queue def initialize @queue = [] end def enqueue(job_data) queue << job_data.merge(:enqueued_at => Time.now) end end |
RSpec 3ではComposable Matchersを使ってこう書くそうです。(リンク先から引用。以下同様。)
1 2 3 4 5 6 7 8 9 10 11 12 |
describe BackgroundWorker do it 'puts enqueued jobs onto the queue in order' do worker = BackgroundWorker.new worker.enqueue(:klass => "Class1", :id => 37) worker.enqueue(:klass => "Class2", :id => 42) expect(worker.queue).to match [ a_hash_including(:klass => "Class1", :id => 37), a_hash_including(:klass => "Class2", :id => 42) ] end end |
test-unitではこう書きます。RSpecは「フレームワーク側が」必要な値だけ比較するという方針ですが、test-unitは「テストを書く側が」必要な値だけ取り出して比較するという方針です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
class BackgroundWorkerTest < Test::Unit::TestCase class EnqueueTest < self def test_order worker = BackgroundWorker.new worker.enqueue(:klass => "Class1", :id => 37) worker.enqueue(:klass => "Class2", :id => 42) assert_equal([ {:klass => "Class1", :id => 37}, {:klass => "Class2", :id => 42} ], normalize_queue(worker.queue)) end private def normalize_queue(queue) queue.collect do |job_data| { :klass => job_data[:klass], :id => job_data[:id], } end end end end |
リンク先ではテスト結果の失敗時にどのように報告するかについても触れています。enqueue
の実装がコメントアウトされていたときを例にしています。
1 2 3 4 5 6 |
class BackgroundWorker # ... def enqueue(job_data) # queue << job_data.merge(:enqueued_at => Time.now) end end |
RSpec 3の場合は次のようになって読みやすい、ということです。英語で読みくだせるのがポイントですね。
1) BackgroundWorker puts enqueued jobs onto the queue in order Failure/Error: expect(worker.queue).to match [ expected [] to match [(a hash including {:klass => "Class1", :id => 37}), (a hash including {:klass => "Class2", :id => 42})] Diff: @@ -1,3 +1,2 @@ -[(a hash including {:klass => "Class1", :id => 37}), - (a hash including {:klass => "Class2", :id => 42})] +[] # ./spec/background_worker_spec.rb:19:in `block (2 levels) in <top (required)>'
test-unitの場合は次のようになります。RSpecとは対照的に、英語を極力排除して実際のプログラムとデータを見せる方に注力しています。
Failure:
test_order(BackgroundWorkerTest::EnqueueTest)
test-worker.rb:22:in `test_order'
19: worker.enqueue(:klass => "Class1", :id => 37)
20: worker.enqueue(:klass => "Class2", :id => 42)
21:
=> 22: assert_equal([
23: {:klass => "Class1", :id => 37},
24: {:klass => "Class2", :id => 42}
25: ],
<[{:id=>37, :klass=>"Class1"}, {:id=>42, :klass=>"Class2"}]> expected but was
<[]>
diff:
? [{:id=>37, :klass=>"Class1"}, {:id=>42, :klass=>"Class2"}]
Compound Matcher Expressionsという機能は次のように書ける機能ということです。これまではstart_with
のチェックとend_with
のチェックを別に書かなれければいけなかったのに、一緒に書けるようになったということです。
1 |
expect(alphabet).to start_with("a").and end_with("z") |
test-unitではこう書きます。期待するパターンなら特定の文字列に置換します。一回で比較するという方針は同じです。
1 2 3 |
alphabet = "abcxyz" a_z = "a...z" assert_equal(a_z, alphabet.gsub(/\Aa.*z\z/, a_z)) |
次のように期待したパターンでない場合は元の文字列が変わらないので失敗します。
1 2 3 |
alphabet = "abcxyZ" a_z = "a...z" assert_equal(a_z, alphabet.gsub(/\Aa.*z\z/, a_z)) |
失敗時のメッセージにはちゃんと元の文字列がでるので、実際の値はなんだったのか、という情報が失われることはありません。
Failure:
test_a_z(AlphabetTest)
test-alphabet.rb:7:in `test_a_z'
4: def test_a_z
5: alphabet = "abcxyZ"
6: a_z = "a...z"
=> 7: assert_equal(a_z, alphabet.gsub(/\Aa.*z\z/, a_z))
8: end
9: end
<"a...z"> expected but was
<"abcxyZ">
diff:
? a...z
? bcxyZ
RSpec 3の例はもっとたくさんありますが、2個だけ紹介しました。
値の比較の仕方に方針の違いがでていました。
RSpec 3はより英語らしく読み書きできるようになりそうですね。
test-unitはあいかわらずRubyらしく読み書きできるテスティングフレームワークですね。