fix-flaky-tests について
Fix Flaky Testsは、断続的に失敗するテストを調査し、対象を絞ってコードを修正し、繰り返し実行して確認するワークフロースキルです。TuistのテストデータやXcodeのテスト実行を扱う開発者向けです。
主な機能
- 01不安定と判定されたテストの検索
- 02信頼性指標と実行履歴の確認
- 03失敗内容とソース位置の確認
- 04よくあるタイミングや状態の問題の特定
- 05テストの反復実行による修正確認
活用シーン
- 01プロジェクト内の不安定なテストのトリアージ
- 02失敗箇所からテストのソースを特定
- 03反復または並列実行で修正を確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ tuist/ agent-skills --skill fix-flaky-testsインストール後、Claude Code で/fix-flaky-testsを入力して読み込まれたか確認します。
詳細
不安定なテストの修正
クイックスタート
通常、Tuist のテストケースの URL または識別子を受け取ります。次の手順で調査し、修正してください。
tuist test case show <id-or-identifier> --jsonを実行して、テストの信頼性メトリクスを取得します。tuist test case run list Module/Suite/TestCase --flaky --jsonを実行して、不安定な実行のパターンを確認します。- 失敗した不安定な実行に対して
tuist test case run show <test-case-run-id> --jsonを実行し、失敗メッセージとファイルパスを取得します。 - 報告されたパスと行にあるテストソースを読み、不安定なパターンを特定して修正します。
- テストを複数回実行し、安定して成功することを確認します。
特定のテストが指定されていない場合は、以下の「検出」セクションから始めてください。
検出
特定のテストケースが指定されていない場合は、プロジェクト内のすべての不安定なテストを見つけます。
tuist test case list --flaky --json --page-size 50
これにより、現在不安定と判定されているすべてのテストケースが返されます。主なフィールドは次のとおりです。
module.name/suite.name/name— テスト識別子avg_duration— 優先順位付けに役立ちます(まず高速なユニットテストを修正します)is_quarantined— テストがすでに隔離されているかどうか
トリアージ戦略:
- テストをスイートごとにグループ化します。同じスイート内に不安定なテストが複数ある場合、共通の根本原因があることがよくあります。
- 失敗が同じ
test_run_idを共有しているか確認します。同じ実行で失敗したテストは、個々のテストの不具合ではなく、プロセスのクラッシュによって終了した可能性があります。 - 失敗メッセージを確認して分類します。テストロジックの不具合か、インフラストラクチャの問題(ネットワークエラー、サーバーの 502 エラー、再試行時の競合)かを見極めます。
調査
1. テストケースのメトリクスを取得する
UUID または Module/Suite/TestCase 形式の識別子を渡せます。
tuist test case show <id> --json
tuist test case show Module/Suite/TestCase --json
主なフィールドは次のとおりです。
reliability_rate— 成功した実行の割合(高いほど良好です)flakiness_rate— 過去 30 日間に不安定と判定された実行の割合total_runs/failed_runs— 実行数の参考情報last_status— 現在の状態
2. 不安定な実行履歴を確認する
tuist test case run list Module/Suite/TestCase --flaky --json
識別子には ModuleName/SuiteName/TestCaseName 形式を使用します。スイートがない場合は ModuleName/TestCaseName 形式を使用します。不安定と検出された実行のみが返されます。
3. 実行履歴全体を確認する
tuist test case run list Module/Suite/TestCase --json --page-size 20
次のようなパターンを探します。
- 特定のブランチで失敗していますか?
- CI(
is_ci: true)でのみ失敗しますか、それともローカルでも失敗しますか? - 失敗は特定のコミット前後に集中していますか?
4. 失敗の詳細を取得する
tuist test case run show <test-case-run-id> --json
主なフィールドは次のとおりです。
failures[].message— アサーションまたはエラーメッセージfailures[].path— ソースファイルのパスfailures[].line_number— 失敗した正確な行failures[].issue_type— 問題の種類(assertion_failure など)repetitions— 存在する場合は、再試行の動作(成功/失敗の順序)を示しますtest_run_id— この実行が属する、より大きなテスト実行crash_report— クラッシュレポートデータ(テストランナーがクラッシュした場合に存在します)。exception_type、signal、exception_subtype、triggered_thread_framesが含まれます
コード分析
failures[0].pathのファイルを開き、failures[0].line_numberに移動します。- テスト関数全体と、そのセットアップ/ティアダウンを読みます。
- 以下のよくある不安定なパターンのどれに該当するかを特定します。
- 同じスイート内のほかのテストと状態を共有していないか確認します。
よくある不安定なパターン
タイミングと非同期処理の問題
- 待機の不足:非同期処理の完了前にテストが結果を確認しています。修正方法:
await、タイムアウト付きの期待値、またはポーリングを使用します。 - 競合状態:複数の並行処理が共有状態にアクセスしています。修正方法:アクセスを同期するか、直列キューを使用します。
- タイムアウトのハードコード:
sleep(1)や固定の遅延が CI では短すぎます。修正方法:固定の遅延ではなく、条件に基づく待機を使用します。
共有状態
- テストの汚染:あるテストがグローバル状態または静的状態を変更し、別のテストがその状態に依存しています。修正方法:setUp/tearDown で状態をリセットするか、テストごとに固有のインスタンスを使用します。
- シングルトンの汚染:共有シングルトンがテスト間で状態を保持しています。修正方法:依存関係を注入するか、シングルトンをリセットします。
- ファイルシステムの残留物:テストが後続の実行に影響するファイルを残しています。修正方法:一時ディレクトリを使用して、後片付けを行います。
環境依存
- ネットワーク呼び出し:テストでは、応答が遅い、または利用できない可能性がある実際のサービスにアクセスします。対処法:ネットワーク呼び出しをモックします。
- 日時への依存:テストが現在の時刻やタイムゾーンに依存します。対処法:時計を注入するか、時刻を固定します。
- ファイルシステムのパス:環境によって異なるハードコードされたパスです。対処法:相対パスまたは一時ディレクトリを使用します。
実行順序への依存
- 暗黙の順序依存:必要な状態を設定する別のテストの後に実行した場合のみ、テストが成功します。対処法:各テストを自己完結させます。
- 並列実行時の競合:単独では動作するテストが、同時に実行すると失敗します。対処法:テストごとに一意のリソースを使用します。
クラッシュ(crash_report で特定)
EXC_BREAKPOINT/SIGTRAP— nil の強制アンラップ、Swift の事前条件違反EXC_BAD_ACCESS/SIGSEGV— 解放後の使用、またはダングリングポインターEXC_CRASH/SIGABRT— 捕捉されていない Objective-C 例外
修正の実装
パターンを特定したら、次の手順を実行します。
- 根本原因に対処する最小限の修正を適用します。
- 関連のないコードはリファクタリングしないでください。
- モックやヘルパーなどのテスト用ユーティリティが必要な場合は、新しく作成する前に既存のものがないか確認します。
検証
テストの繰り返し実行
xcodebuild に組み込まれている繰り返し実行機能を使って、失敗するまで特定のテストを繰り返し実行します。
xcodebuild test -workspace <workspace> -scheme <scheme> -only-testing <module>/<suite>/<test> -test-iterations <count> -run-tests-until-failure
これにより、テストは最大 <count> 回実行され、最初の失敗時に停止します。テストにかかる時間に応じて反復回数を選択してください。高速なユニットテストの場合は 50~100 回、低速な統合テストや受け入れテストの場合は 2~5 回にします。
修正前の再現
修正を適用する前に、不安定な失敗をローカルで再現できるか試してください。再現に成功すると、根本原因の分析を裏付けられ、修正を直接検証できます。上記の「テストの繰り返し実行」の方法を使うか、並行処理が疑われる場合は、以下の競合状態の再現方法を使ってください。
特に競合状態、CI 固有のタイミングの問題、環境依存の失敗など、一部の不安定なシナリオはローカルでの再現が困難、または不可能な場合があります。妥当な範囲で試しても再現できない場合は、コード分析と失敗ログに基づいて修正を進めてください。明確な不具合の証拠(例:同期されていない共有状態、TOCTOU パターン)に裏付けられた修正であれば、ローカルで再現できなくても有効です。
競合状態の再現
競合状態や並行処理のバグは、CI レベルの並列実行でのみ発生することが多く、ローカルでの再現が困難です。次の方法を順に試してください。
- 並列度を上げる:
-parallel-testing-enabled YESを追加して、テストスイートを並行して実行します。 - より広範なテストスイートを実行する:単一のテストではなく、モジュール全体(例:
-only-testing ModuleTests)を実行し、共有リソースの競合を増やします。 - スレッドサニタイザー:TSan を有効にして実行し、データ競合を確実に検出します。注:TSan によるオーバーヘッドでタイミングが変化し、TSan の実行時には一部の競合が発生しない場合があります。
xcodebuild test -workspace <workspace> -scheme <scheme> -only-testing <module> -enableThreadSanitizer YES
- 広い範囲での高い反復回数:上記の方法をすべて組み合わせ、並列実行と多くの反復回数でモジュール全体を実行します。
ローカルで競合状態を再現できなくても、コードがスレッドセーフでないことが明らかな場合(例:共有状態を同期せずに変更している場合)は、修正は有効です。上記と同じ再現方法でテストが成功することを確認し、修正を検証してください。コミットメッセージには、コード分析と失敗ログに基づいて特定した、CI でのみ発生する競合状態への対処であることを記載してください。
完了チェックリスト
- 不安定さの根本原因を特定した
- 対象を絞った修正を適用した
- テストが一貫して成功することを(複数回実行して)検証した
- 新たなテスト依存関係や共有状態を導入していない
- 内容を適切に説明するメッセージを付けて修正をコミットした