CI(継続的インテグレーション、コードの変更を自動でビルド・テストする仕組み)のパイプラインで、テストが失敗しては再実行し、緑のチェックが出た瞬間にマージする運用をしていないでしょうか。
この記事は、CIのグリーンチェックを条件反射でマージ許可に使っているエンジニアや、クラウド環境で障害の再現性に悩むSRE(サイト信頼性エンジニア)向けに書いています。テストの再実行と障害対応の再現テスト、監視のアラート運用には、実は同じ落とし穴があります。その構造を整理して、今日から見直せるポイントをまとめました。
グリーンチェックは「体温計」であって「診断書」ではない
CIのグリーンチェックは、そのときの一時的な健康状態を示す体温計のようなものです。マージしてよいという診断書ではありません。
再実行しても失敗したり成功したりするテストのことを、フレーキーテスト(flaky test、不安定なテスト)と呼びます。フレーキーテストを「リトライすれば通る」という運用で放置すると、上限のないリトライがレースコンディション(複数の処理が競合して結果が変わる不具合)を「出荷可能な成果物」に変換してしまいます。
つまり、今回のPRではたまたま緑になっただけで、同じ競合状態は次のPRでも、より遅いランナー(実行環境)でも再現し得るということです。共有フィクスチャ(テスト用の固定データ)がこの問題を増幅させます。あるテストが tests/fixtures/users.json を書き換え、次のテストがその少し新しいバージョンを読んでしまう、という順序依存が典型例です。
この構造は、クラウド運用における「アラートは鳴らなかったから正常」という誤解と同じです。監視が緑でも、しきい値の設定が甘いだけかもしれません。CIのグリーンチェックと監視ダッシュボードの緑ランプは、どちらも「今この瞬間、観測範囲内で異常が見えていない」という限定的な情報でしかありません。
機械的なゲートを3つ用意する
この問題への対処は、精神論ではなく機械的なゲート(自動判定の関所)を用意することです。3つの仕組みを順番に見ていきます。
1つ目は、pre-push フック(コードをリモートに送る前に自動実行されるスクリプト)です。ローカルにしか存在しないフックは単なる提案にすぎません。.githooks/pre-push としてリポジトリにコミットし、GitHub Actions(GitHubのCI/CD実行基盤)からも同じスクリプトを呼び出すことで、フックを設定し忘れたメンバーも同じ基準で弾かれるようにします。
このフックは3種類の差分を拒否します。tests/fixtures/MANIFEST.txt に列挙されていないフィクスチャファイルの変更、チケット番号(TICKET=)を伴わない新規の pytest.mark.skip、そして .snap や .golden といったスナップショットファイルのみの変更です。特に3つ目は厳しいルールですが、スナップショットだけを差し替えるPRは、ゴールデンファイル(期待値を固定したテスト基準)が実態と乖離していく典型的な経路なので、緩めない方がよいとされています。
2つ目は、フィクスチャの分離粒度です。テスト用の一時ディレクトリをモジュール単位で共有すると、一見速く見えますが、ファイル名の衝突によって状態が漏れます。関数単位(pytestの tmp_path フィクスチャなど)でディレクトリを分離することで、AIエージェントが生成したテストのようにファイル名を使い回すケースでも安全に隔離できます。
3つ目は、後述するフレーキー予算です。これらは互いに補完し合う仕組みなので、どれか1つを省略すると残りの効果も弱くなります。
クラウド障害対応との構造的な共通点
この3段構えのゲート設計は、クラウド移行後の障害対応フローと驚くほど似ています。
オンプレミス環境からクラウドへ移行すると、ネットワーク遅延やスポットインスタンスの中断など、以前は起きなかった「たまに失敗する」事象が増えます。これはCIのフレーキーテストと同じ性質の問題です。原因を潰さずに自動リトライやオートスケーリングで隠すと、障害の予兆が可観測性(オブザーバビリティ、システム内部の状態を外部から把握できる度合い)のログに埋もれてしまいます。
たとえば、AWSのAuto ScalingやKubernetesのPodの自動再起動は、一時的な障害を吸収する点では便利ですが、根本原因を特定しないまま再起動を繰り返すと、障害の頻度そのものが「仕様」として定着してしまいます。CIにおける無制限リトライと同じ構図です。
ここで有効なのが、フレーキーテストと同様の考え方である「予算」の発想です。障害対応の世界にはSLO(サービスレベル目標)とエラーバジェット(許容できる障害の総量をあらかじめ決めておく考え方)という概念があります。CIにおけるフレーキー予算も同じ発想で、「不安定なテストの再実行回数や許容件数を、こっそり無限に許すのではなく、上限を決めて可視化する」 things です。予算を使い切ったら、リトライで誤魔化さずに根本原因を調査する、というルールをチームで合意しておくことがポイントです。
今日確認できること
自分のプロジェクトが該当するかどうかは、以下の点をチェックすると判断しやすくなります。
- CIの再実行ボタンやリトライ機構に、回数の上限や記録の仕組みがあるか確認する
pytest.mark.skipや@Disabledのようなスキップ指定に、チケット番号などの追跡可能な理由が書かれているか grep で確認する- テスト用の一時ディレクトリやフィクスチャファイルが、テスト間で共有されていないか(モジュールスコープになっていないか)を確認する
- クラウド環境のオートスケーリングやヘルスチェックの再起動ログを見て、同じインスタンス・同じPodが短時間に繰り返し再起動していないか確認する
- 監視のアラートが「鳴らなかった」ことを正常の根拠にしていないか、しきい値設定を見直す
pre-pushフックの導入自体は、.githooks/ ディレクトリを作りスクリプトを置いたうえで、git config core.hooksPath .githooks を実行するだけで試せます。GitHub Actionsのワークフローファイルから同じスクリプトを bash .githooks/pre-push として呼び出せば、ローカルとCIで判定基準を統一できます。まずは使い捨てのリポジトリで試してみて、チームのルールに合わせて調整していくのが現実的です。
まとめ
グリーンチェックも監視の緑ランプも、その瞬間の体温計にすぎません。マージや障害復旧の判断根拠にするには、機械的なゲートで裏付けを取る必要があります。
CIでは、フックの共有・フィクスチャの分離・フレーキー予算の可視化という3段構えが有効です。クラウド運用では、これをSLOとエラーバジェットの運用、そして自動リトライの上限管理に置き換えて考えられます。
まずは自分のCI設定でリトライ回数の上限とスキップ理由の記録ルールがあるかを確認し、クラウド側ではオートスケーリングのログに繰り返し再起動が埋もれていないかを見直すところから始めてみてください。