フレーキーテストとの戦い — 信頼できないテストが信頼を蝕む

コードにもテストにも変更がないのに、実行するたびに合格したり失敗したりするテストを「フレーキーテスト」と呼ぶ。一見些細な現象だが、「よく落ちるが気にしなくていい」という空気が定着するとテストスイート全体の信頼が崩れ、本物のバグまで見過ごされる。原因の分類、検知の手法、Googleの大規模な実測データ、そして隔離・リトライ・根絶という各アプローチの功罪、そしてこれらを支えるCI/CDの仕組みを辿る。

testingflaky-testsci-cdtest-automationreliability

「たまに落ちる」が奪うもの

前回のカバレッジとミューテーションテストでは「テストの質」をどう測るかを扱った。だがどれほど質の高いテストを書いても、そのテストが気まぐれに落ちるなら、テストスイートは信頼という最も大切な資産を失う。

フレーキーテスト(flaky test)とは、コードにもテストコードにも一切変更がないのに、実行するたびに合格したり失敗したりするテストのことである。真のバグによる失敗は再現性があるが、フレーキーな失敗は「もう一度実行したら通った」という形で現れる。失敗の原因が、プロダクトコードの欠陥でもテストロジックの誤りでもない——ここがフレーキーテストの厄介さの核心だ。

一見すると「たまに落ちるだけの些細な問題」に見える。しかしその実害は、テストという営みの土台そのものを静かに崩していく。

なぜ有害か

フレーキーテストの害は数値に表れにくいところに潜む。

  • 信頼の毀損: 「赤(失敗)が出ても再実行すれば通る」という経験が積み重なると、CIの失敗シグナルそのものへの信頼が失われる
  • “狼少年”効果: 本当のリグレッションが起きても「どうせフレーキーだろう」と見過ごされ、バグが本番へ流出する
  • 開発速度の低下: 失敗が本物のバグかフレーキーかの切り分けに時間を取られる。特にリリース直前の「赤の常態化」はデプロイを遅らせる
  • 心理的コスト: 原因不明の失敗を繰り返し目にすることは、開発者のテストへのモチベーションを削ぐ

テストの価値は「変更を恐れずに済む自信」を与えることにある。フレーキーテストはその自信を裏側から蝕む。

原因の分類

フレーキーさの原因は多岐にわたるが、大きく次のように整理できる。

分類 典型例
非決定性 乱数のシード未固定、HashMapなど順序不定のコレクション依存、浮動小数点の丸め
時間・タイムアウト依存 現在時刻に依存したロジック、CI負荷に対して短すぎるタイムアウト、日付境界(深夜0時・月末)をまたぐ
並行性・レース条件 非同期処理の完了を待たずにアサーションする、スレッド間の実行順序に依存
順序依存 あるテストが後続テストの前提状態を暗黙に作る、実行順が変わると失敗
テスト間の状態共有 グローバル変数・静的フィールド・共有DBレコードのクリーンアップ忘れ
外部依存 実際のネットワーク越しの外部API呼び出し、相手側の障害・遅延の影響を受ける
リソース枯渇 CI環境のメモリ・CPU不足、並列実行によるポート競合やディスク不足

これらは単独でなく複合して現れることも多い。たとえば「非同期の完了待ちが不十分」なテストは、CI環境のCPU負荷が高い日にだけ失敗する——「並行性」と「リソース枯渇」が絡み合った形だ。

検知 — 再実行差分とフレーク率

フレーキーテストを見つける最も基本的な方法は、同一コミットに対して同じテストを複数回実行し、結果が揺れるものを洗い出すことである。

  • 再実行差分(rerun diff)法: 失敗したテストを変更なしで数回〜数十回再実行し、一度でも結果が変われば「フレーキー」と判定する
  • フレーク率(flakiness rate)計測: 一定期間の実行に対する「不一致な結果」の比率を継続的に記録し、閾値を超えたテストを自動フラグする
  • 統計的シグナル: 「同じコミットハッシュに対してpassとfailが両方観測された」という事象をCIログから機械的に抽出する

GitHub Actions、Jenkins、Buildkite などのCIやテストレポーティングツールには、こうした再実行・フレーク検知の機能を備えるものが増え、検知自体の自動化が定着しつつある。

Googleの大規模な実測

フレーキーテストが「個々のプロジェクトの不注意」ではなく大規模開発に構造的に付随する課題であることを示す一次資料として、Googleが2016年にテスティングブログで公開した数字がよく引用される。

  • 全テストのうち約16% が、何らかのレベルのフレーキーさを示す(エンジニアが書いたテストの7個に1個以上が偶発的に失敗しうる)
  • テスト実行全体で見ると、約1.5% の実行結果が「フレーキー」と判定される
  • passからfailへの遷移のうち約84% がフレーキーなテストによるもので、これが「新しい失敗は本物か否か」の調査コストを大きく押し上げる

同社はさらに、フレーキーテストを除去する取り組みと新たに混入する取り組みがほぼ同じペースで進み、「一定のフレーク率と共存し続ける」構造的な状態にあると指摘している。

auto-wait — E2Eという最大の温床への構造的対策

E2E・ブラウザテストの世代交代で見たとおり、UIの描画完了・非同期通信・アニメーション終了などタイミングに起因するフレーキーさは、E2E層に特に多い。旧世代(Seleniumなど)では固定時間のsleepが多用され、これ自体が「短すぎれば失敗、長すぎれば遅い」という温床になっていた。

Cypress や Playwright など新世代のツールは auto-wait(自動待機) を標準搭載し、要素が実際に操作可能な状態(表示され、アニメーションが終わり、無効化されていない等)になるまで自動でポーリングして待つ。これにより固定sleepへの依存を減らし、フレーキーさの主要な発生源の一つを構造的に緩和した。

隔離とリトライ — 応急処置の功罪

フレーキーと判明したテストへの現実的な運用が2つある。どちらも「共存策」であって「解決策」ではない。

quarantine(隔離) は、フレーキーなテストをCIの合否判定から一時的に外し、別枠で実行し続ける運用だ。本流のCIをブロックせず開発速度を保てる利点がある一方、「一時的」のはずが放置されて恒久化しやすい。隔離された期間、そのテストは本物のリグレッションを検知する力を失う。隔離リストが際限なく増えれば、スイート全体の実効カバレッジが目減りする。

リトライは、失敗したテストを自動で数回再実行し、一度でも成功すれば合格扱いにする運用だ。実装が容易で即効性がある反面、真のリグレッション(プロダクト側のタイミングバグ)を「リトライで通ったから問題なし」と覆い隠すリスクがある。リトライ回数がインフレし、実行時間も増える。

いずれも、誰が・いつまでに直すかという所有権とセットでなければ、単なる「見て見ぬふり」の制度化に終わる。

根絶へ — 設計問題としてのフレーキーさ

本質的な対処は、原因を分類表に沿って特定し、設計レベルで取り除くことである。

  • hermetic化: テストが必要とする状態(時刻・乱数・外部依存)をすべてテスト自身が制御する(大規模開発のテストのhermetic testsを参照)
  • 時刻・乱数の固定: システムクロックへの直接依存をやめ、注入可能な時刻取得・乱数シードに置き換える
  • テストの独立性: 各テストが自分の必要とするデータ・状態を自己完結的にセットアップ・破棄し、暗黙の順序依存を断つ
  • 非同期の明示的な完了待ち: sleepでなく、対象の状態変化をポーリングするかコールバック/Promiseの完了を待つ
  • 外部依存のモック化: 実ネットワーク呼び出しをテストダブルに置き換え、外部サービスの可用性・レイテンシから切り離す

フレーキーテストの多くは、設計上の問題(暗黙の依存、境界の欠如)が症状として現れたものだと理解できる。

CI/CDという舞台

フレーキーさが最も痛みを伴うのは、テストが自動で回り続けるCI/CDパイプラインの上である。現代のパイプラインは、単体→統合→E2Eと段階的にテストを実行し、速く安価なものを先に、遅く高価なものを後に配置する(fail-fast)。テスト数が増えれば並列化・シャーディングで実行時間を抑え、変更に関係するテストだけを選ぶテスト影響分析(Test Impact Analysis) で無駄を削る。

この文脈で「マージ前にテストを通す」ことがゲートになる以上、フレーキーテストはゲートそのものを不安定にする。だからこそ、フレーク検知・隔離・リトライといった運用は、CI/CDの信頼性を保つための仕組みとして組み込まれる。テストの自動化は、テストの信頼性という前提の上でしか価値を生まない。

まとめ

フレーキーテストは「たまに落ちるだけの些細な問題」ではなく、テストスイート全体への信頼を静かに蝕む構造的な課題である。原因は非決定性・時間依存・並行性・状態共有・外部依存など多岐にわたり、Googleの実測が示すように大規模な現場では無視できない規模で発生する。隔離やリトライは症状を緩和する現実的な運用だが、根本解決には設計レベルでの独立性・hermetic性の確保が要る。

ここまで見てきたテストは、いずれも「実行して確かめる」営みだった。だが実行によるテストには原理的な限界がある——第1話で置いたDijkstraの警句「テストはバグの存在を示せるが、不在は示せない」である。次の記事では、この限界に別の側から挑む形式手法を扱う。

参考文献

  • John Micco, “Flaky Tests at Google and How We Mitigate Them”(Google Testing Blog, 2016)
  • Google Testing Blog, “Where do our flaky tests come from?”(2017)
  • 各E2Eツール(Cypress / Playwright)公式ドキュメントの auto-wait / retry に関する記述
← Back to ソフトウェアテストの系譜