フレーキーテスト
フレーキーテストとは
フレーキーテスト(flaky test)は、コードを変えていないのに結果が変わるテストです。10回に1回落ちる、CI では落ちるが手元では通る、Firefox だけ落ちる、といった形で現れます。
厄介なのは、テストが信号として機能しなくなることです。赤を見ても「またあれか」と再実行で済ませる習慣がつくと、本物の回帰が混じっても見過ごされます。[[CI/CD]] のパイプラインにおいて、フレークは「たまに落ちる」以上の損失です。
なぜ起きるか — 4つの原因
| 原因 | 典型例 |
|---|---|
| タイミング | 描画や非同期処理の完了を固定時間の sleep で待っている。マシンが遅いと間に合わない |
| 共有状態・実行順序 | 前のテストが残した localStorage やキャッシュ、DB の行が次のテストに影響する。並列実行や順序の入れ替えで顔を出す |
| 資源の逼迫 | 別のジョブと同時に走って CPU を取り合い、タイムアウト境界のテストが落ちる |
| 外部依存 | 実ネットワークや実サービスを叩いている。相手の遅延や停止で結果が変わる |
多くの場合、テストは「壊れている」のではなく前提を暗黙に置いているだけです。「この処理は5秒で終わる」「前のテストは何も残さない」「ネットワークは速い」といった前提が、環境によって崩れます。
再実行で済ませてはいけない理由
再実行は症状を隠すだけで、次の3つを引き起こします。
- 本物の回帰を見落とす — フレークと本物の失敗は同じ赤で表示される。再実行の習慣は両方を無視する習慣になる
- 後続の工程が止まる — 失敗すると後段のジョブ(デプロイや同期)が skipped になる構成では、フレーク1件で本番への反映が止まる。しかも「落ちた」ことは見えても「反映されなかった」ことは別の検査で初めて分かる
- コストが積み上がる — E2E の再実行は数十分単位。並行して走る PR が増えれば、CI の実行枠や課金上限に達する
「原因を突き止めて直す」か「原因が分かるまで隔離する」かのどちらかを選び、無言の再実行は選択肢から外します。
切り分けの手順 — 「単独で通った」は根拠にならない
フレークの調査で最も多い誤判定が、単独実行で通ったから環境要因だと結論することです。単独でも実行のたびに通ったり落ちたりするのがフレークなので、1回通った事実は何も証明しません。
決め手は実測です。
- 所要時間を測る — 落ちるテストの各ステップ(初期化、遷移、待機)に計測を仕込み、内訳をログに出す。「初期化だけでタイムアウトの4割を使っていた」のような事実が出てくる
- 環境ごとの差を見る — 同じテストを chromium / firefox / webkit で測ると、特定のブラウザだけ数倍遅いことがある。その場合は実装の回帰ではなく、待ち時間の前提が崩れている
- 基準と比べる — 変更前のコミットを別の作業ツリーに展開し、同じ条件で回す。基準でも同じ頻度で落ちるなら、今回の変更が原因ではない
- 同時に走っているものを確認する — レビュー用の並列ジョブや別のビルドと同時なら、まずそれを止めて単独で回す
「たぶんこれだろう」で対処を始めると、直したつもりで別の要因が残ります。数字を1つ取ってから直します。
直し方
- 時間ではなく条件を待つ —
sleep(3000)ではなく「要素が表示されるまで」「リクエストが完了するまで」を待つ。[[Playwright]] の自動待機はこのために存在する - テストごとに状態を初期化する — localStorage・Cookie・キャッシュ・DB を各テストの前にリセットする。保存先を1つ増やしたら、リセット処理にも1つ足す
- タイムアウトは根拠を持って決める — 実測した所要時間に余裕を乗せる。遅いブラウザや重い画面だけ個別に伸ばす設定を使い、全体を一律に延ばさない
- 外部依存を差し替える — 単体・結合の層では [[テストダブル(モック・スタブ)]] で外部を切る。E2E で実サービスを叩くなら、その揺れを織り込んだ判定にする
- 隔離には期限を付ける — 原因が分からないテストを
skipにするなら、Issue 番号と期限をコメントに残す。無期限の skip は「消したテスト」と同じ
初学者向けポイント
- 「たまに落ちる」と聞いたら、まず落ちる頻度と落ちる環境を記録します。頻度が分からないと、直ったかどうかも分かりません
- 固定時間の待ちをテストに書いた瞬間、そのテストはフレークの候補です。条件で待てないか考えましょう
- 手元(Windows)と CI(Linux)で結果が違うときは、改行コードなど環境差そのものが原因のこともあります([[改行コード(CRLF・LF)]])
関連技術とのつながり
- [[自動テスト]] — テスト層の全体像。フレークは外側の層ほど起きやすい
- [[Playwright]] — 自動待機と、プロジェクト単位のタイムアウト設定
- [[CI/CD]] — フレークがパイプライン全体を止める構造
- [[テスト駆動開発]] — 先に書くテストが決定的であるほど、後からのフレークが減る
- [[テストダブル(モック・スタブ)]] — 外部依存という揺れの源を切る道具
- [[並行処理と非同期]] — 並列実行で顔を出す共有状態の問題
- [[改行コード(CRLF・LF)]] — 環境要因と誤認されやすい失敗の実例
Q: フレーキーテストの説明として正しいものはどれ?
- [ ] 実装のバグで毎回必ず落ちるテスト
- [x] コードを変えていないのに、通ったり落ちたりするテスト
- [ ] 実行に時間がかかるテスト
解説: 結果が実行ごとに変わり、赤が信号として機能しなくなるのが問題の本質です。
Q: 「単独で実行したら通った」から環境要因だと結論してはいけない理由はどれ?
- [x] フレークは単独でも実行ごとに結果が揺れるため、1回通った事実は何も証明しないから
- [ ] 単独実行は CI と違うコマンドを使うから
- [ ] 単独実行ではテストがスキップされるから
解説: 決め手は所要時間などの実測です。ステップごとの内訳を測り、基準のコミットと比べて切り分けます。
Q: フレークへの対処として本文が推奨しているものはどれ?
- [ ] 落ちたら無言で再実行する
- [ ] タイムアウトを全体で一律に10倍にする
- [x] 固定時間の待ちを条件の待ちに変え、テストごとに状態を初期化する
解説: 再実行は症状を隠すだけで、一律の延長は根拠がありません。実測に基づいて待ち方と初期化を直します。