はじめに
以下のテストで次の2点をテストしようとしています
- 特定の条件でraiseが発生しているか
- エラーメッセージが正しく出力されているか
このテストの何が悪いか皆さんは分かりますか?
sample_code.rb
class SampleCode
def self.animal_check(animal)
raise '猫はアレルギーだからダメ' if animal == 'cat'
raise '犬は可愛すぎるからダメ' if animal == 'dog'
pp '他の動物は許す'
end
end
test.rb
test '猫はアレルギーだからraise' do
assert_raises(RuntimeError, '猫はアレルギーだからダメ') do
SampleCode.animal_check('cat')
end
end
とあるプロジェクトのテストコードを眺めていたところ、陥りがちなあるミスを発見したので戒めとして記事にしようと思います。
では、なぜこのテストコードが悪いかについて解説していきます。
このテストの何がいけないのか?
答えはassert_raises
の使い方です。
実は以下のようにコードをいじってもこのテストは通ってしまいます。
test.rb
test '猫はアレルギーだからraise' do
assert_raises(RuntimeError, 'どんなエラーメッセージでも落ちないよ〜') do
SampleCode.animal_check('cat')
end
end
assert_raisesの解説
assert_raisesが具体的にどんな挙動をするかを見ていきましょう。
assert関連のメソッドが定義されているassertions.rb
には、以下のようにassert_raises
が定義されています。
assertions.rb
def assert_raises *exp
flunk "assert_raises requires a block to capture errors." unless
block_given?
# assert_raisesの最後の引数が文字列の場合は、それをテストの失敗メッセージとする。
msg = "#{exp.pop}.\n" if String === exp.last
exp << StandardError if exp.empty?
begin
yield
rescue *exp => e
pass
return e
rescue Minitest::Assertion
raise
rescue SignalException, SystemExit
raise
rescue Exception => e
flunk proc {
exception_details(e, "#{msg}#{mu_pp(exp)} exception expected, not")
}
end
exp = exp.first if exp.size == 1
flunk "#{msg}#{mu_pp(exp)} expected but nothing was raised."
end
上記コードを見ながら、初めに出したテストコードの何が悪かったかを解説していきます。
test.rb
test '猫はアレルギーだからraise' do
assert_raises(RuntimeError, '猫はアレルギーだからダメ') do
SampleCode.animal_check('cat')
end
end
assertions.rb
のコードを見れば、第二引数の「猫はアレルギーだからダメ」はRuntimeErrorが発生しなかった際にテストの失敗メッセージとして表示されるということがわかりますね。
つまり、このテストコードでやっているのは、RuntimeErrorのエラーメッセージとして「猫はアレルギーだからダメ」が出力されるテストではないのです。
実際にやっていることは以下の2つなのです。
- RuntimeErrorになればテスト成功
- テスト失敗時には「猫はアレルギーだからダメ」をテストの失敗メッセージとして出力する
どうすればエラーメッセージをテストできるの?
以下のようにすればエラーメッセージのテストが可能となります。
test.rb
test '猫の場合はraise' do
exception = assert_raises(RuntimeError) do
SampleCode.animal_check('cat')
end
assert_equal('猫はアレルギーだからダメ', exception.error_message)
end