RSpec でテストが失敗したときにより詳しい情報を得るには?
RSpec でテストが失敗したとき、もちろんその原因を探るわけですが、そのときにどんな手段を思いつきますか?
ローカル環境であれば binding.b(byebug や pry など)で処理を止めて、コンソールで調査することもできるでしょう。でも、CI 上でのみ再現する flaky なテストに遭遇したときはどうでしょうか?
この記事では、そんなケースでも少し役に立つ「失敗時のメッセージをカスタマイズする」ためのテクニックを紹介します。
expect(...).to matcher, message の第2引数って?
RSpec では以下のように書くことがあります。
expect(actual).to eq(expected)
この .to メソッド、実は第2引数に「失敗時のメッセージ」を渡すことができます。
expect(actual).to eq(expected), "ちがいます"
失敗した場合、この "ちがいます" が表示されます。
さらに便利なのは、このメッセージをブロックで渡せるという点です。
expect(actual).to eq(expected), -> { "ちがいます: actual=#{actual.inspect}" }
ブロックは マッチャが失敗したときのみ評価される ため、
失敗時の状態をログのように埋め込むことができます。
CI 環境で flaky test を探るときにも
CI 上でのみ失敗するようなテストは、再現が難しいからこそ原因調査が大変です。
そんなとき、以下のように「失敗時の値やコンテキスト」をメッセージに含めておくと、ログだけで判断できることもあります。
expect(result).to eq("成功"), -> {
<<~MSG
結果が一致しませんでした。
実際の値: #{result.inspect}
ユーザーID: #{user.id}
リクエストパラメータ: #{params.inspect}
MSG
}
このようにしておけば、CI のログからでも「なにがどう失敗したのか」を追いやすくなります。
仕組み
RSpec::Expectations::ExpectationHelper.handle_failure の第2引数、 message の中で評価されています。
.not_to や raise_error のときは?
このテクニックは .not_to や expect { ... }.to raise_error など、ほとんどのマッチャでも使えます。
RSpec の matcher はすべて RSpec::Expectations::ExpectationHelper.handle_failure を経由して評価されるためです。
まとめ
RSpec の .to メソッドに第2引数やブロックを渡すことで、失敗時のメッセージをカスタマイズできます。
これは単にメッセージを変えるだけでなく、「CI 上でしか再現しないテストのデバッグ情報をログに残す」という観点でもとても有用です。
再現の難しいバグに出会ったとき、少しでもその原因に近づけるように、このテクニックをぜひ試してみてください。
Discussion