RSpec 3の新機能であるComposable Matchersの使い方の例をtest-unitならどう書くか紹介します。リンク先のコードを示し、それのtest-unitバージョンを示す、という流れを繰り返します。
テスト対象
テスト対象は次のコードです。
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を使ってこう書くそうです。(リンク先から引用。以下同様。)
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は「テストを書く側が」必要な値だけ取り出して比較するという方針です。
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
の実装がコメントアウトされていたときを例にしています。
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
Compound Matcher Expressionsという機能は次のように書ける機能ということです。これまではstart_with
のチェックとend_with
のチェックを別に書かなれければいけなかったのに、一緒に書けるようになったということです。
expect(alphabet).to start_with("a").and end_with("z")
test-unitではこう書きます。期待するパターンなら特定の文字列に置換します。一回で比較するという方針は同じです。
alphabet = "abcxyz"
a_z = "a...z"
assert_equal(a_z, alphabet.gsub(/\Aa.*z\z/, a_z))
次のように期待したパターンでない場合は元の文字列が変わらないので失敗します。
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は「フレームワーク側が」必要な値だけ比較するという方針
- test-unitは「テストを書く側が」必要な値だけ取り出して比較するという方針
- 参考:デバッグしやすいHTMLのテストの書き方の「おまけ: assert_match問題」のところ。
RSpec 3はより英語らしく読み書きできるようになりそうですね。
test-unitはあいかわらずRubyらしく読み書きできるテスティングフレームワークですね。