# Gradleの不安定なテスト {#gradle-flaky-tests}

不安定なテストとは、同じコードで複数回実行したときに結果（合格または不合格）が異なるテストです。これらはテストスイートへの信頼を損ない、誤った失敗の原因調査に開発者の時間を浪費させます。Tuistは不安定なテストを自動検出し、経時的な追跡を支援します。

![不安定なテストページ](/images/guides/features/test-insights/flaky-tests-page.png)

## 不安定な検出の仕組み {#how-it-works}

Tuistは以下の2つの方法で不安定なテストを検出します。

### テストの再試行 {#test-retries}

再試行機能を使用してテストを実行すると、Tuistは各試行の結果を分析します。一部の試行で失敗し、他の試行で合格するテストは、不安定としてマークされます。

[Test Retryプラグイン](https://github.com/gradle/test-retry-gradle-plugin)や同様の仕組みを使用して、失敗したテストを再実行できます。Tuistは、一部の試行で合格し、他の試行で失敗するテストを検出します。

プラグインを `build.gradle.kts` に追加します。

```kotlin
plugins {
    id("org.gradle.test-retry") version "1.6.2"
}

tasks.test {
    retry {
        maxRetries = 3
        maxFailures = 5
        failOnPassedAfterRetry = false
    }
}
```

![不安定なテストケースの詳細](/images/guides/features/test-insights/flaky-test-case-detail.png)

### クロスラン検出 {#cross-run-detection}

テストの再試行を行わなくても、Tuistは同じコミットおよび同じGradleプロジェクトに対する異なるCI実行間で結果を比較することで、不安定なテストを検出できます。あるCI実行でテストが合格し、同じコミットおよびプロジェクトの別の実行で失敗した場合、両方の実行が不安定としてマークされます。

Gradleプロジェクトは比較キーの一部です。これは、同じテストでもプロジェクトによって決定的に異なる動作をする可能性があるためです。例えば、異なる依存関係でコンパイルされた共有テストクラスや、異なるモジュールフィクスチャ上で実行される場合などです。異なるGradleプロジェクト上での同じコミットの2回の実行は、別個の実行バリアントとして扱われ、互いを不安定としてフラグ付けしません。

これは、再試行で捕捉できるほど一貫して失敗しないものの、依然として断続的なCI失敗を引き起こす不安定なテストを捕捉するのに特に役立ちます。

## 不安定なテストの管理 {#managing-flaky-tests}

### 自動クリア

検出とクリアは、プロジェクト上の **自動化アラート** を介して実行されます。すべてのプロジェクトには、デフォルトの「不安定なテスト検出」自動化が用意されており、その *トリガー* はテストを不安定としてマークし、 *リカバリー* は、テストが設定されたリカバリーウィンドウ（デフォルト **14日** ）の間に再トリガーされずに経過すると、フラグをクリアします。 **設定 → 自動化** で編集して、リカバリーウィンドウを変更したり、リカバリーアクション（例：隔離解除も行う）を入れ替えたり、リカバリーを完全に無効にして、手動でクリアするまでテストが不安定としてマークされたままになるように設定できます。

### 手動管理

テストケースの詳細ページから、テストを手動で不安定としてマークしたり、マークを解除したりすることもできます。これは以下の場合に便利です。
- 修正作業中に既知の不安定なテストであることを認識したい場合
- インフラストラクチャの問題により、テストが誤ってフラグ付けされた場合

## 不安定なテストの隔離 {#quarantining}

隔離は、不安定なテストを分離し、修正中にCIをブロックしないようにします。デフォルトでは **手動アクション** です。ダッシュボードのテストケース詳細ページから隔離、隔離解除、モードの切り替えを行います。ただし、 **設定 → 自動化** の下の **自動化アラート** に組み込むこともでき、不安定さが閾値を超えるとテストが自動的にミュート（またはスキップ）され、回復すると隔離が解除されます。手動か自動かを問わず、すべての遷移はテストケースの監査ログに記録されます。

デフォルトでは、リカバリーはトリガー条件が成立しなくなるまで待機するため、テストは古い不安定な実行がトリガーウィンドウから外れるまで隔離されたままになります。より早く回復させるには、 **回復条件** を **新しい不安定な実行なし** に設定します。これにより、新しい不安定な実行なしでリカバリーウィンドウ（日数または実行回数）が経過するとテストは回復し、新しい不安定な実行が発生するたびに待機が再開され、回復後は新しい不安定な実行のみが再び隔離を引き起こします。

隔離されたテストは、以下の2つのモードのいずれかになります。

- **ミュート**：テストは引き続き実行されますが、プラグインは `ignoreFailures = true` を設定し、隔離されていない失敗のみでビルドを再失敗させます。失敗は依然として不安定テスト検出器に供給されるため、ビルドを壊さずにテストを監視し続けることができます。積極的に調査中のテストにこのモードを選択してください。
- **スキップ**：プラグインはGradleの `excludeTestsMatching` を介してテストをフィルタリングするため、テストは決して開始されません。新しい結果は生成されず、再度有効にするまで不安定テストダッシュボードから除外されます。テストが壊れている、遅い、またはあまりにも持続的に不安定で実行することがCI時間の無駄である場合にこのモードを選択してください。

### 隔離されたテストが合格として表示される理由 {#quarantined-passing}

- **ミュートされたテスト** は引き続き実行されます。失敗したミュートテストは、テストケース実行上で **失敗** として記録され、不安定としてフラグ付けされます。テストごとのステータスは書き換えられません。上書きされるのは **全体のテスト実行** です。実行内のすべての失敗しているテストケースがミュートされている場合、実行全体は合格として報告されるため、ミュートされた失敗はCIを中断しません。
- **スキップされたテスト** はまったく実行されないため、ダッシュボードにはテストの最後の実際の実行からのステータスが表示され続けます。そのスナップショットは数週間前のものである可能性があります。

### 隔離の有効化 {#enabling-quarantine}

隔離は、 `CI` 環境変数が設定されている場合、CI上で **自動有効** になり、ローカルビルドでは無効になります。プラグインは各テストタスクの前にTuistサーバーから隔離されたテストのリストを取得します。ミュートされたテストは通常通り実行され、その失敗はマスクされます。スキップされたテストはフィルタリングされます。

これを `settings.gradle.kts` で明示的に制御できます。

```kotlin
tuist {
    testQuarantine {
        enabled = true  // or false to disable
    }
}
```

`enabled` が設定されていない場合、デフォルトは自動検出になります。CI上では有効、ローカルでは無効です。

## 新しいテストのストレステスト {#stress-testing-new-tests}

ストレスゲートは、ブランチが追加したテストケースをそれぞれ新鮮なプロセスで数回再実行し、不安定になったものを報告します。数週間後ではなく、それを導入したプルリクエスト上で新しい不安定なテストを確認できます。

テストケースは、プロジェクトのデフォルトブランチで過去90日間CIで実行されていない場合、またはそれ以降にそのブランチに着陸したコミット上で実行されていない場合、新しいものとカウントされます。マージキューを使用する場合、キューが実行したテストケースは、キューのコミットが着陸すると既知となり、キューがそれをドロップした場合、新しいままとなります。コミットは、TuistがCI上で収集する <.localized_link href="/guides/features/test-insights/gradle#git-history">Git履歴</.localized_link> を介して一致させられます。

### 有効化

ゲートはデフォルトでオフです。モードを設定します。

- **`report`**：各不安定なテストケースについて警告します。実行はその自身の結果に基づいて終了します。
- **`enforce`**：不安定なテストケースは実行を失敗させ、失敗したテストと同じ終了コードを返します。

数週間は `report` から始めて、`enforce` が何をブロックしていたかを確認してください。

これを `settings.gradle.kts` で宣言するか、CIレーンごとにモードを変更するために `TUIST_TEST_STRESS_NEW_TESTS` を設定します。

```kotlin
tuist {
    stressNewTests {
        mode = "report" // or "enforce"
    }
}
```

隔離とは異なり、このゲートにはCIを意識したデフォルト設定がありません。`mode` が設定されていない場合、どこでも実行されません。

### 各テストの再実行回数

最初のパスでテストケースにかかった時間によって異なります。

| 最初のパス | 再実行回数 |
| --- | --- |
| 最大5秒 | 10 |
| 最大10秒 | 5 |
| 最大30秒 | 3 |
| 最大5分 | 2 |
| 5分以上 | 再実行なし |

各再実行はその1つのテストケースにフィルタリングされたネストされたGradleビルドであるため、すべての再実行で新しいテストJVMが使用されます。このパスは200テストケースまたは10分のいずれかに達すると停止し、どの制限に達したかを報告します。

### ゲートが実行されない場合

- 最初のパスですでに失敗しています。まずそれらのテストを修正してください。
- プロジェクトにデフォルトブランチがないか、そのブランチ上でCIで何も実行されていません。
- プロジェクトのテストケースの30%以上が新しいように見えます。それほど多くを再実行するのはコストに見合いません。これは、Tuistへのレポートを始めたばかりのプロジェクトや、一度に多くのテストを移動させる名前変更の後によく発生します。

## Slack通知 {#slack-notifications}

Slack統合で <.localized_link href="/guides/integrations/slack#flaky-test-alerts">不安定なテストアラート</.localized_link> を設定することで、テストが不安定になった瞬間に通知を受け取れます。

## 不安定および隔離状態のクエリ {#querying}

### CLI

`tuist test case` コマンドツリーは、Tuistがテストケースについて追跡するすべての情報を公開します。

```sh
tuist test case list --flaky                       # only flaky test cases
tuist test case list --quarantined                 # only muted or skipped test cases
tuist test case show <test_case_id>                # detail: flakiness rate, last status, run counts
tuist test case events <test_case_id>              # audit log: marked_flaky, muted, skipped, ...
tuist test case run list <test_case_id>            # run history with status and duration
tuist test case run show <test_case_run_id>        # single run, including failure breakdown
```

これらすべてはスクリプト用に `--json` を受け入れます。

### REST API

同じデータはHTTP経由で利用可能です。ルート、フィルター、レスポンスフィールドの完全なリストについては、APIリファレンスの [テストケースエンドポイント](https://tuist.dev/api/docs#tag/test-cases) を参照してください。テストケースの状態（不安定のマーク付け/解除、ミュート、スキップ）を変更するには、`is_flaky` または `state` を指定して `PATCH /api/projects/{account_handle}/{project_handle}/tests/test-cases/{test_case_id}` を送信します。同じ変更は `tuist test case update` として、また `update_test_case` Model Context Protocolツールとしても利用可能です。
