Tabelog Tech Blog

食べログの開発者による技術ブログです

AIを活用したFlaky Testの原因分析と効率化:大規模サービスを支えるテスト安定化への取り組み

はじめに:大規模サービスを悩ませるFlaky Testという課題

こんにちは。食べログカンパニー 開発本部 飲食店プロダクト開発部 運用改善チームの @4palace です。 食べログには2019年から参画しています。

「食べログ」は、月間利用者数9400万人、掲載店舗数88万店舗以上、月間PV数23億を超える大規模サービスです。 https://tabelog.com/advertisement

このサービスを安定的にお届けするには、正常に稼働するビルドパイプラインと、品質を担保する自動テストを整備し続ける必要があります。 食べログはRuby on Railsで開発されており、そのコード量は非常に膨大です。 その品質を担保するRSpecの自動テスト件数も同じく膨大です。 本記事で紹介する取り組みを開始した2025年7月時点で50800件を超えていました。日々増えており、現在は54000件を超えています。

もちろん、テストの多さが品質を裏付ける絶対的な指標ではありません。 しかし、膨大なテストが常に成功する状態を維持できていれば、いつでも自信を持って素早くリリースでき、より良いサービスの開発に集中できます。

一方で、いつも失敗するわけではなく、気まぐれに失敗するテストがあります。 これがFlaky Testと呼ばれるもので、食べログでもこの問題に悩まされてきました。 最終的に本番デプロイするメインブランチに限っても、約300件のFlaky Testが存在していました。

全体のテスト50800件に対して300件のFlaky Testは、割合にすると0.6%です。 この割合は一見すると小さいように思えるかもしれませんが、単体テストはすべて成功することが前提です。 そのため、1つでも失敗があれば、原因の調査や対応が必要になります。

テストが失敗した場合、まず「このテストの失敗はFlakyなのか、それとも意図しない破壊(デグレード)なのか?」を判断する必要があります。 Flaky Testであれば、CIをリランすることで解消されることも多いのですが、リランには余分な時間がかかります。 一方、プロダクションコードの不具合であれば、コードの切り戻しや修正対応の判断が必要です。 このような判断や対応の遅れがリリース遅延につながることもありました。 また、デプロイ前の調査自体が本来の開発タスクを圧迫することも問題でした。

単体テストの作成および管理は多くのチームにまたがっており、私たちのチームだけで勝手に修正すると、仕様に反してしまう可能性があります。 そのため、私たちのチームだけで修正するのではなく、各開発チームへ修正を依頼する必要がありました。 そこで、私たちのチームがFlaky Testの状況を取りまとめ、各チームへの協力依頼を行うことになりました。

Flaky Testの集計、原因分析、改善には、AIを利用し、実際の改善タスクを進めていきました。 本記事では、私たちがFlaky Testの撲滅活動を進めるにあたり、どのようにAIを活用したかを紹介します。

なぜAIでの分析を試みたのか

これまでのFlaky Test調査は、担当者の経験則に依存する解析作業となっており、時間と手間がかかっていました。 また、この属人化がチーム横断での改善を進める上での依頼しづらさにもつながり、ボトルネックとなっていました。

この課題に対し、AIには以下のような幅広いサポートを期待していました。

  1. データ収集・整形ツールの開発支援
    まず、分析の元となる全Flaky Testの情報をCircleCIから網羅的に収集し、分析しやすい形式に整形する必要がありました。このツールの開発自体を、AIに支援してもらえるのではないかと考えました。
  2. ホットスポット分析
    約300件という膨大なエラーログ全体をAIに読み込ませることで、複数のテストに共通する失敗パターンや、修正による影響範囲が大きい「ホットスポット」を特定してもらうことを期待しました。
  3. 個別テストの原因解析と修正方針の提案
    考えられる原因仮説だけでなく、具体的な修正方針の提案までをAIに出してもらうことを期待しました。

これらの期待は、単なる技術的な興味からだけではありませんでした。300件ものFlaky Testを修正するには、多くの開発チームの協力が不可欠です。 そこで私たちは「旗振り役」として、AIによるホットスポット分析で「修正インパクトの大きい問題」や「原因が共通している問題群」を特定し、修正の優先順位付けを行った上で各チームに依頼するという、効率的な進め方を構想していました。

この構想のもと、AIを活用したFlaky Testの撲滅活動を始めました。

AIを活用したFlaky Test分析の軌跡

調査データの収集

まず、「調査データの収集」から始めました。

食べログのCI/CDツールとして、CircleCIを利用しています。 各ビルドの成功/失敗や、失敗したテストに対する「Flaky Test」のタグ付けなどは、すでにCircleCIで行われていました。 そのため、まずはWeb UIからデータを取得できないか試みましたが、網羅的なデータを一括で収集するには、UIが最適ではありませんでした。 また、そもそもFlaky Testのタグづけされていないが、一定の割合で失敗するFlaky Testである、というケースもありました。

幸い、CircleCIは情報取得用のAPIを提供していたため、このAPIを利用して情報を取得し、使いやすいフォーマットへ整理しました。 そこで、内製ツール FlakyTest Log Collector を作成し、CircleCI APIからテスト結果ログを網羅的に収集しました。 CircleCIのFlaky Test一覧取得APIや、JobIDからArtifactを取得するAPIを利用し、必要な情報を揃えて表にまとめつつ、AIの解析に利用しやすいJSON形式にも整えるツールを作成しました。

今回は各チームに依頼し、進捗を把握しながら進めたかったため、表形式で管理できるCSVを使い、以下の内容が自動で出力されるようにしました。

  • 失敗テスト名
  • 最終失敗日時
  • 失敗したRSpecのパス
  • 失敗時の生ログ

また、各チームの進捗や分担を管理できるよう、以下の情報を記載できる欄も付与しました。

  • 対応リリース日
  • 担当チーム
  • ステータス
  • 修正issueのURL

一方で、AIに解析させることも想定していたので、失敗時の生ログ、失敗したRSpecのパス、失敗日時などはJSON形式で出力させ、以下のような解析用のファイルも別途生成するようにしました。

{
  "test_name": "",
  "job_number": 12345,
  "workflow_created_at": "2025-07-09T00:48:30Z",
  "artifact_path": "test-results/result.xml",
  "failure_spec_path": "spec/models/concerns/xxx_spec.rb",
  "failure_line_in_spec": 277,
  "message": "expected `last_updated_at` to have changed to 2025-07-09 10:04:19.000000000 +0900, but is now 2025-07-09 10:04:20.000000000 +0900",
  "details": "(具体的なスタックトレース)"
}

なお、このツールの作成にはAIコードエディタであるCursorを活用し、AIと対話しながら開発を進めることで、低コストでの作成が実現しました。 プロトタイプの作成は1日で完了しています。 利用したいAPIのフォーマットが決まっており、入出力の仕様などを対話形式でやりとりすることで、すぐに形にできました。

これにより、必要な全量データの調査と収集をスムーズに行うことができました。

AIによるFlaky Testの原因解析

当初のもくろみ

ツールで作成したJSONをもとに、300件近い失敗ログを格納したJSONファイルとソースコードをCursorに入力し、以下のプロンプトでホットスポット分析を試みました。 これにより、ホットスポットの分析から全Flaky Testの修正方針立案まで、一気に完了できると期待していました。

あなたは、Ruby on RailsとRSpecに精通したシニアバックエンドエンジニアです。
専門家として、技術的に正確かつ簡潔な分析を提供してください。
ユーザーが `flaky_results.json` の内容を提供したら、以下の思考プロセスと出力フォーマットに厳密に従って、統合解析レポートを生成してください。

(以下には、入力jsonファイルの読み解き方、最も多い想定失敗テスト原因をホットスポットとして抽出するレポートを出力するルールを記載)

なお、モデルは複数のモデル(GPT-4o、Claude 3.5 Sonnetなど)を試した結果、今回はGemini 2.5 Proを採用しました。 単純に失敗ログの行数が多いため、その問題解析に耐えうる広大なコンテキストウィンドウを持つモデルを採用したかったのが理由です。 Gemini 2.5 Proは100万トークンという広大なコンテキストウィンドウを持つため、これを活かし、ソースコード全体と複数の関連テスト、スタックトレースを一度に読み込ませることで、コード間の複雑な依存関係や、テスト実行順序に起因する問題を推論できると期待していました。

ホットスポット分析の失敗

これでホットスポット分析まで一気にできる!と意気込んでいましたが、実際はそう甘くありませんでした。

  • 「テストデータの不備」といった指摘はするものの、具体的にどのデータの何が問題なのかを示さないため、次のアクションに繋げられない。
  • 「同様のエラーがほか多数」のように具体性に欠ける表現をするため、結局問い直しが発生する。
  • エラー件数自体を間違えるなど、レポート自体の出力品質が低い。
  • スタックトレースを正しく解釈できず、エラーの根本原因とは異なる箇所を修正するような提案が多い。
  • 提示された「修正方針」通りに修正しても、その正しさを検証する方法が示されず、妥当性の判断が困難でした。

上記のように品質の悪い解析結果が多く、修正コードどころかホットスポット分析自体もあまり精度の良いものは得られませんでした。

後から振り返ると、以下の3つの失敗が原因だと考えています。

  • 失敗その1:AIへ丸投げした結果、コンテキストが溢れてしまった
  • 失敗その2:AIが立てた仮説を検証する手立てがなかった
  • 失敗その3:AIに完璧な修正を求めてしまった

失敗その1:AIへ丸投げした結果、コンテキストが溢れてしまった

1つ目は、そもそも入力した情報が多すぎたという失敗です。

約300件のエラーログを一括で入力し、「ホットスポットを特定・分析して解決策を提示せよ」と一度に要求したため、焦点がぼやけた回答しか得られませんでした。 いかに広大なコンテキストウィンドウを持っていたとしても、大きすぎる入力は良い結果に繋がらないことを学びました。

この経験から、AIの役割を「個別のログに対する深い洞察を得ること」に絞り、一度に与える情報量を適切にコントロールすることが重要だとわかりました。

失敗その2:AIが立てた仮説を検証する手立てがなかった

2つ目は、AIが立てた仮説の検証方法が明確でなかったという失敗です。

AIは様々な可能性を考え、もっともらしい仮説を提示してくれます。 しかし、それを確認する手段までは提示してくれませんでした。 AIが「仮説は正しい」と主張し続けたものの、その内容は誤っており、他の可能性の調査を遅らせてしまうことがありました。

一例として、AIの主張と実際のパターンが異なっていたケースを紹介します。

AIは、「このテストはbeforeで時刻が固定されているため、時刻依存の問題は発生していない」と主張していました。 RSpecのコード上も、主張と同じく時刻を固定する処理が記載されており、一見正しそうでした。 しかし、対象のテストは深夜に毎日実行されるナイトリービルドでのみ失敗しており、日中のビルドでは成功していました。 この事実から、時刻依存の可能性を拭いきれず、再調査しました。

調査の結果、beforeブロックで設定された時刻固定処理が実行されるより前に、let!による変数評価が行われていたことが判明しました。このlet!ブロック内の処理が時刻に依存していたため、時刻の固定が間に合っていなかったのです。

そこで、AIには「その仮説が正しいのであれば、それを証明するための具体的な確認方法を提示してください」というプロンプトを与えるようにしました。 これにより、「正しいならこうなるはず」という確認方法もセットで検証でき、真因の特定につながりました。

この経験から、AIの提案はあくまで仮説の1つとして扱いました。そのうえで、人間によって検証することが重要だとわかりました。

失敗その3:AIに完璧な修正を求めてしまった

3つ目は、AIにそのままマージできるレベルの修正をいきなり求めてしまったという失敗です。

当初は、失敗ログとソースコードがあれば、AIが完璧な修正を一度に行ってくれるのでは、という期待がありました。 しかし、実際には場当たり的に修正したり、影響範囲を考慮できずに他の箇所へ影響を与える修正を提案したりすることが多く、うまくいきませんでした。

この経験から、AIに完璧な修正を期待するのをやめました。 真因と考えられる部分と、その検証方法を出力することに特化させ、AIを「分析パートナー」として位置づけることが重要だと気づきました。

戦略の転換:AIによる全体分析から、人間とAIの協業へ

ホットスポット分析の失敗から、私たちは当初の「AIに全体を分析させて、賢くタスクを振り分ける」という戦略が機能しないことを学びました。

そこで、私たちはアプローチを転換しました。 AIが苦手な「全体を俯瞰した大まかなグルーピング」は人間が行い、人間が苦手な「個別のテストに対する深く、多角的な原因分析」をAIに任せる、という役割分担です。

具体的には、FlakyTest Log Collector が出力したCSVファイルを使い、まず私たち自身がエラーメッセージの傾向や失敗時刻、失敗ファイルのパスなどから、「これは時刻関連の問題っぽい」「これはDBのデータ整合性の問題だろう」といった大まかな障害のグルーピングを行いました。

そして、そのグルーピングに基づいて各開発チームに調査と修正を依頼しました。

私たちのチームが担当することになった障害や、原因の特定が特に難しいと判断された個別のテストについては、改善したプロンプトを使ってAIによる詳細な分析を適用することにしました。

失敗を踏まえたプロンプトの改善

失敗を踏まえ、プロンプトを以下のように見直しました。

改善のポイント

  • 全テストではなく個別のテストに絞り込み
  • ホットスポット分析ではなく、原因仮説の列挙に変更
  • 仮説だけでなく、検証方法も併せて提示させる

改善前のプロンプト(全体分析)

あなたは、Ruby on RailsとRSpecに精通したシニアバックエンドエンジニアです。
専門家として、技術的に正確かつ簡潔な分析を提供してください。
ユーザーが `flaky_results.json` の内容を提供したら、以下の思考プロセスと出力フォーマットに厳密に従って、統合解析レポートを生成してください。

(以下には、入力jsonファイルの読み解き方、最も多い想定失敗テスト原因をホットスポットとして抽出するレポートを出力するルールを記載)

改善後のプロンプト(個別分析)

{失敗したテスト単体のJSONデータ}

このテストがFlakyになる原因仮説を、プロダクションコード側、テストコード側の両面から、可能性が高い順に5つ挙げてください。
また、それぞれの仮説を検証するための具体的なコマンドやコードも提示してください。

テストコード、およびテスト対象のプロダクションコードは以下です。
{テストコードのファイルパス}
{実装コードのファイルパス}

全テストではなく解析したいテストに絞ることで、ソースコードの解析にコンテキストを集中させることができます。 また、いきなり完成品のコードを求めるのではなく、仮説とその検証方法をセットで示してもらうことで、検証可能な形で仮説を提示してくれるようになりました。 これにより、網羅的かつ検証可能な解析結果を出力させることができるようになりました。

個別テストの原因分析

この改善されたプロセスを通じて、個別のFlaky Testに対する原因分析の精度と速度を大幅に向上させることができました。

当初の期待と、実際のところ

「なぜAIでの分析を試みたのか」の章で挙げた以下の3点について、AIは実際のところどうだったか、という観点で振り返ります。

  1. データ収集・整形ツールの開発支援
  2. ホットスポット分析
  3. 個別テストの原因解析と修正方針の提案

データ収集・整形ツールの開発支援

データ収集や全量把握のための分析ツールの作成には、期待通り、もしくは期待以上の効果がありました。 CircleCIのAPI仕様にあまり詳しくない状態から始めたにも関わらず、わずか1日でFlaky Testの一覧取得・整理・出力ができるツールのプロトタイプが完成しました。 最初から明確な仕様がなくとも、自分の中に以下のようなぼんやりとしたイメージがあれば、AIが良い方向に導いてくれることを体感しました。

  • (情報源として)何が利用できるか?
  • 何のために必要な出力なのか?
  • どんな形で出力したいか?

何度でも収集・出力できるツールを迅速に作成できたことは、手分けして作業を進める上でも非常に役立ちました。

ホットスポット分析

一方、これは期待通りには進みませんでした。 約300件のエラーログ全体を一度にAIへ入力し、傾向を分析させようと試みましたが、得られたのは一般的な回答に留まりました。 当初の目的だった「AIの分析結果をもとに効率的にタスクを振り分ける」という旗振り役としての役割は、このアプローチでは果たせませんでした。

この経験から、300件ものエラーログを一度に投入して全体の傾向を分析させる方法ではうまくいかない一方で、人間がある程度当たりをつけてグルーピングし、個別のテストに絞って原因を深掘りさせる使い方では、AIは絶大な力を発揮することがわかりました。

個別テストの原因解析と修正方針の提案

これは期待とは少し異なる結果となりました。 当初は、「そのままマージしても良いような具体的な修正コードが出来上がること」を期待していました。 しかし、AIは原因解析を間違うことも、修正範囲が大きくなりすぎることもあり、提案をそのまま採用できないケースが多くありました。

そこで途中で方針転換し、多様な原因仮説を提示してもらいました。さらに「その仮説が正しいなら、この手順で再現するはず」という確認手順も併せて用意することで、人間が仮説の信憑性を判断し、真因の特定に役立てられました。 つまり、人間の思考の選択肢を広げ、意思決定を補助する役割に徹してもらうことで、非常に高い価値を感じました。

分析の成果をチームの資産に変える

さて、これまでの取り組みをもとに、チームごとに手分けして実際に修正を進めていきました。 すると、「こういう作り方をするとFlakyになりうる」というFlaky Testのパターンがいくつか見えてきました。 これを形式知として残すことで、個人の能力に依存しない知識をチームに残せると感じました。 ナレッジの蓄積は重要ですが、一から資料を作成するのはコストがかかるため、つい後回しにしがちです。

そこで実際の事例から、形式知としてAIに書き残してもらいました。

プロンプトは以下の通りです。

FlakyTest対策のナレッジをきちんと残したい。ある対策ナレッジの概要から、
・なぜいけないのか(FlakyTestを誘発するのか)
・どうすればいいのか(NG例から、修正案まで)
をMarkdown形式で生成してほしい。
Ruby on Railsのコードでお願いします。なぜFlaky Testが起きるのかわからない人にも理解できるよう、簡潔に説明してください。

以下のトピックでお願いします。

let / let! と初期化順序の意識: テストデータの生成順序に依存関係がある場合、let!の安易な使用は避け、beforeブロックで初期化の順序を明示的に制御する。これにより、テストの意図が明確になる。

すると、以下のような出力が得られました。 内容は一部省略していますが、具体的なコード例まで記載されており、読みやすいナレッジがすぐに出来上がりました。

# Flaky Test対策: let / let! と初期化順序の意識
## 概要
テストデータの作成順序に依存関係がある場合、let!の安易な使用はテストを不安定(Flaky)にする原因となります。
データの作成順序が重要である場合は、**letでデータの定義を行い、beforeブロック内で呼び出すことで生成順序を明示的に制御する**のが堅牢な書き方です。

## なぜ問題なのか?

RSpecにおけるletとlet!の挙動の違いを理解することが重要です。

*   **let(遅延評価)**: ブロック内のコードは、テストケース(itブロック)内で**初めて呼び出されたとき**に一度だけ実行されます。
*   **let!(即時評価)**: ブロック内のコードは、各テストケースが**実行される前**に必ず一度実行されます。

let!は便利ですが、その実行順序は**コードに書かれた定義順**に依存します。この「暗黙の順序依存」がFlaky Testの温床となります。

(...中略...)

## どのように修正するか?

letは「どのように作るか」の**定義**に徹させ、実際のデータ作成(副作用を伴う処理)の**実行順序**はbeforeブロックに集約します。
これにより、誰が読んでもデータの作成順序が明確になり、意図しない順序の変更が起こりにくくなります。

### NG例

let!の定義順序に暗黙的に依存しており、非常に壊れやすいテストです。もしlet!(:post)がlet!(:user)より先に定義されていたら、このテストは失敗します。

(NGコード例)

### OK例

letで各データの作り方を定義し、beforeブロック内でそれらを呼び出すことで、**生成の順序を明示的に制御**します。
userが先に作られ、その次にそのuserを使ってpostが作られる、という依存関係がコードから明確に読み取れます。

(OKコード例)

このようにletとbeforeブロックを組み合わせることで、テストの前提条件(Arrange)が明確になり、
可読性が向上し、意図しない順序変更によるFlaky Testを未然に防ぐことができます。

これにより、個人の経験を形式知へと気軽に変換できました。 この情報をもとにコードレビューで指摘したこともあり、ノウハウの伝達がしやすくなりました。

また、この情報はMarkdownで書き起こしたため、これを元に新たなFlaky Testを解析する際のコンテキストとしてAIに与えることもできました。 これを活かすことで、解析の精度自体も向上していきました。

成果と今後の展望

各チームの協力のもとで取り組みを進めた結果、メインブランチで散発的に発生していたFlaky Testを撲滅できました。 (もちろん、私一人の力ではなく、ご協力いただいた多くのチームの方々のおかげです) 過去の深夜帯ビルドでは300件近くのFlaky Testが発生していましたが、現在は安定したビルドを実現しています。

今回のFlaky Test撲滅活動でAIを活用して得られた成果をまとめます。

  • 定量的効果
    • 300件以上あったメインブランチのFlaky Testを撲滅
    • Flaky Testの原因調査時間を削減
      • これまでは平均1〜2時間程度かかっていた原因調査が、30分未満で完了するようになった
  • 定性的成果
    • CIの安定性向上(定常リリース時間帯だけでなく、深夜帯でも安定してビルドが成功するようになった)
      • リリース遅延リスクの低減
      • 開発者のリリース時の判断コストが減少
    • Flaky Testの撲滅ノウハウが形式知化された
      • 問題発生時の修正方法だけでなく、新たなFlaky Testを生まないためのノウハウがドキュメント化できた

今回はメインブランチのFlaky Test撲滅に取り組みましたが、これによってテストが安定したことで、よりビルド/リリースの高速化を目指し、CIのRSpec実行パイプラインの並列数を増やすことを検討しています。 すると、並列数を増やしたり、RSpecの実行順をランダムにしたりすることで、新たにFlaky Testとなるケースが現れました。 これらについても同じ手順で収集・分析し、チーム横断で協力しながら修正を進めることで、さらなるRSpecの安定化と高速化を目指して取り組んでいます。

今回の過程で得られた効果的なプロンプトや、AIとの対話ノウハウは、それ自体がチームの貴重な資産になります。 これらをチーム内で共有・改善していくことで、AI活用のスキルを属人化させず、チーム全体の分析能力を底上げしていきたいと考えています。 また、AIにドキュメントの草案を作成させるアプローチは、個人の暗黙知をチームの形式知へと効率的に変換する強力な手段だとわかりました。 今後も、様々な場面でこの仕組みを活用し、ナレッジの共有を促進していきます。

また、今回のAI活用の経験は、他の技術的課題へのアプローチにも活かせます。

「必要なデータ収集ツールをAIで素早く作成」
→「AIに仮説と検証方法を提案してもらう」
→「人間が検証し、真因を特定」

今回はこのような流れで撲滅を進めましたが、これはパフォーマンス調査や、本番での複雑なエラーの調査といった他の技術課題でも応用可能な方法だと考えています。 AIを活用することで、これまで取り組みづらかった技術課題の解決にも挑戦していきます。

おわりに

AIは強力なパートナーであり、人間の思い込みや問題に対する偏った見方をうまく矯正してくれるポテンシャルを秘めています。 しかし、AIを使いこなすには人間のコンテキスト整理が非常に重要であり、人間が主体となって検証・判断することが不可欠です。

この記事が、皆さんの現場でAIとの新たな関係を築き、複雑な課題に立ち向かうための一助となれば幸いです。

最後まで読んでいただき、ありがとうございました。

最後に、食べログでは一緒に働く仲間を募集しています! 食べログに興味を持ってくださった方は、以下の採用情報リンクからぜひご応募ください!