CI で稀に失敗してしまうテストへの対処方法

技術部の福森です。

クックパッドでは RSpec と Jenkins を利用して CI による自動テストを行なっています。

テストの数は 12000 examples を越えていて、テストによっては稀に失敗する物が出てきています:

  • 時間帯依存で失敗してしまうもの
  • 他に同時に実行されるテストに依存しているもの (並列実行で組合せが変わり再現する)
  • インテグレーションテストでの ajax リクエストの微妙なタイムアウト
  • etc

また、本番環境を壊さないよう、 CI で成功したリビジョンのみデプロイ可能となっており、開発者が push しデプロイしたいと思っている時に無関係な原因で失敗する事を避けたいという欲求があります。 なぜなら、再度ビルドを実行する時間 (およそ 10 分) の間待たされる事になるからです。

そこで、そのようなテスト起因での失敗を減らし、かつ開発者にそれらを修正してもらうための取り組みを本記事では紹介します。

まず、以下の 2 つの特殊な CI ジョブを実行しています:

  1. 夜間に最後に成功したリビジョンを繰り返し実行し、稀に、あるいは深夜に再現する失敗を発見しやすくする (以降 allnight ジョブと表記)
  2. timecop.gem を利用して翌月初をシミュレートした環境でテストを実行し、月初に失敗するテストを発見しやすくする

これらによって失敗する可能性のあるテストが判明します。この内 (1) の修正依頼については、 今まで失敗頻度等を見て担当者をgit blame 等で割り当て、手動で修正依頼を行っていました。

手間がかかる、チャットや口頭のため忘れられてしまう、進捗が確認できないといった問題があったため、自動化し、さらに稀に失敗する事が確認できているテストに限って自動でリトライする仕組みを導入しています。

自動で修正を依頼する

上記 allnight ジョブにて、 「直近 1 週間で 2 回以上失敗した」 example について修正が依頼されます。

弊社では GitHub:Enterprise (以降 GHE と表記) を利用しており、GHE 上の issue 機能が進捗の確認という面で優れているため、そこで自動依頼を行っています。

issue

仕組みとしては、RSpecJSON formatter の出力を受け付ける簡単なエンドポイントを用意しておき、失敗した example の情報を CI ジョブの最後で POST します。エンドポイントには簡単な Rails アプリを作りそれを利用しています。

その上でアプリ側で上記条件に当てはまる example が発生した時、GitHub API を利用して issue を作成し、担当者を assign して修正を依頼しています。

CI 実行時に自動でリトライする

また、その条件に一致する example は日中の CI ビルドでは一回まで自動でリトライされるような仕組みも導入しています。

全ての example をリトライ対象にしていないのは、リトライに頼り過ぎ失敗を無視するようになるのを防ぐためです。

リトライはこのような rspec-core へのモンキーパッチを利用し実現していて、 RSpec::Core::Example#retry_needed? というメソッドを追加し、example が失敗した時に呼ばれ返り値が true の時のみリトライされるようになっています。

実際にはそのメソッドに上記の小さな Rails アプリへの問い合わせをする処理を入れていて、他にも別の箇所ではチャットにリトライした example を通知するための処理を入れて実行しています。

retry

.............................................................
*00* [RETRY] ./spec/features/foo_spec.rb:81: …
.....................

この自動リトライの仕組みによって、2 ヶ月で作動した 19 回の内、10 回は失敗になるはずだった CI ビルドを救っています。

まとめ

本記事ではクックパッドがどのように CI をコミットとは関係のない原因で失敗させず、開発サイクルの速度を維持しているかを紹介しました。本記事での取り組み等、私たちは開発サイクルを早く回し、ユーザにより良い価値を作り出せるように日々改善を行っています。