1. ljourm

    Posted

    ljourm
Changes in title
+rspec-retryを導入しようとしたらaggregate_failuresによって動作しなかった話
Changes in tags
Changes in body
Source | HTML | Preview
@@ -0,0 +1,105 @@
+# 概要
+
+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と等しいか?」という必ずエラーとなるテストを書いて実行しています。
+
+```ruby
+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`が全てのテストに適用されていました。
+
+```ruby
+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`が同時に適用されないようにして運用することにしました。
+
+```ruby
+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
+```
+
+# 最後に
+
+理想的な対策までたどり着けませんでしたが、「相性問題だからしょうがない」と言えるところまで調査できたと感じています。
+実用レベルではほとんど問題ないと思いますので、これでしばらく様子を見てみたいと思います。