概要
RSpecのオプションaggregate_failures
とretry
を同時に設定すると動作しなかったため、検証内容と対策を記載します。
aggregate_failures
通常、expectの結果がエラーとなった場合はそこで検証が終了しますが、オプションaggregate_failures
を付けた場合は最後まで検証を続けてくれるようになります。
aggregate_failures
は標準で使用可能です。
以下の解説がとてもわかりやすかったです。
RSpecでテストをまとめて検証する方法
https://qiita.com/yshz/items/21162a9ffeb51fbcbe36
retry
オプションretry
はテストが失敗した時、成功するまで指定回数だけ再実行するというものです。
retry
はrspec-retryの導入によって使用可能となります。
GitHub
https://github.com/NoRedInk/rspec-retry
以下の解説がとてもわかりやすかったです。
rspec-retryを使ってテストを再実行するときの設定方法
https://daipresents.com/2017/rspec-retry/
今回問題となったこと
rspec-retryを導入したところ、それが機能しない場合がありました。
調査した結果、retry
とaggregate_failures
を同時に設定した場合、リトライされないとわかりました。
検証
検証に使用したコードは以下です。
「1は2と等しいか?」という必ずエラーとなるテストを書いて実行しています。
it '1回しか検証されない場合', :aggregate_failures, retry: 2 do
# aggregate_failuresをメタデータとして書いた場合はリトライされない
expect(1).to eq(2)
end
it '2回検証される場合 その1', retry: 2 do
# aggregate_failuresをブロックとして書いた場合はリトライされる
aggregate_failures do
expect(1).to eq(2)
end
end
it '2回検証される場合 その2', retry: 2 do
# aggregate_failuresが未設定の場合はリトライされる
expect(1).to eq(2)
end
原因
ざっくりと原因を説明すると、
- retry側「テストは成功した?(失敗なら再実行するよ)」
- rspecの本体側「(aggregate_failuresのために、エラーを一時的に退避させているから)失敗してないよ」
というような会話をしている状況でした。
このエラーを退避させるかのような動作についてはGitHubにissueがあり、「これは仕様」という結論となっていました。
https://github.com/rspec/rspec-core/issues/2289
また、このissueには対策が記載されていましたが、それをretry
に適用することは、私がコードを確認した限りではできないようでした。
aggregate_failures
+ retry
という組み合わせは相性が悪いと結論付け、その場合の対策を施すことにします。
対策
検証コードのように、aggregate_failures
をメタデータでなくブロックで設定してあげれば、retry
オプションが正しく動作するようになります。
ですが私のプロジェクトの場合、以下のコードによってaggregate_failures
が全てのテストに適用されていました。
RSpec.configure do |config|
config.define_derived_metadata do |meta|
meta[:aggregate_failures] = true unless meta.key?(:aggregate_failures)
end
end
ちなみに、これはRSpecの公式にもほとんど同じコードがあるので正しい使い方なのだと思います。
https://relishapp.com/rspec/rspec-core/docs/expectation-framework-integration/aggregating-failures#enable-failure-aggregation-globally-using-%60define-derived-metadata%60
そのため、以下に修正することによってaggregate_failures
とretry
が同時に適用されないようにして運用することにしました。
RSpec.configure do |config|
config.define_derived_metadata do |meta|
meta[:aggregate_failures] = true unless meta.key?(:aggregate_failures)
meta[:aggregate_failures] = false if meta.key?(:retry)
end
end
最後に
理想的な対策までたどり着けませんでしたが、「相性問題だからしょうがない」と言えるところまで調査できたと感じています。
実用レベルではほとんど問題ないと思いますので、これでしばらく様子を見てみたいと思います。