# Gradle 不穩定測試 {#gradle-flaky-tests}

> [!IMPORTANT]
> **必要條件**
>
> - 已設定<.localized_link href="/guides/features/test-insights/gradle">Gradle 測試洞察</.localized_link>。

不穩定測試係指同一份代碼多次執行時會產生唔同結果（通過或失敗）嘅測試。呢啲測試會削弱你對測試套件嘅信心，亦會浪費開發人員排查虛假失敗嘅時間。Tuist 會自動偵測不穩定測試，並幫你長期追蹤佢哋。


![不穩定測試頁面](/images/guides/features/test-insights/flaky-tests-page.png)

## 不穩定偵測運作方式 {#how-it-works}

Tuist 會以兩種方式偵測不穩定測試：

### 測試重試 {#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 專案上的兩次執行會被視為獨立的執行變體，不會互相標記為不穩定。

這對於捕捉那些失敗頻率不足以被重試抓到、但仍會造成間歇性 CI 失敗的不穩定測試特別有用。

## 管理不穩定測試 {#managing-flaky-tests}

### 自動清除 {#automatic-clearing}

偵測同清除會透過項目上嘅 **自動化警報** 執行。每個項目都會有一個預設嘅「不穩定測試偵測」自動化，佢嘅 *觸發條件* 會將測試標記為不穩定，而 *恢復機制* 則喺測試喺設定嘅恢復期（預設 **14 日**）內冇再觸發之後，就會清除呢個標記。你可以喺 **設定 → 自動化** 入面編輯佢，去更改恢復期、更改恢復動作（例如，同時將測試從隔離中釋放），或者完全停用恢復功能，咁樣測試就會一直保持標記為不穩定，直到你手動清除佢哋。

### 手動管理 {#manual-management}

你亦都可以喺測試案例詳情頁面手動標記或取消標記測試為不穩定。以下情況會有用：
- 你想喺修復問題期間確認已知嘅不穩定測試
- 由於基礎設施問題導致測試被錯誤標記


## 隔離不穩定測試 {#quarantining}

隔離會將不穩定測試案例獨立處理，令佢喺你修復期間唔會阻塞 CI。預設係**手動操作**：你可以從儀表板嘅測試案例詳情頁面隔離測試案例、解除隔離同切換模式。你亦可以將佢接駁到**設定 → 自動化**底下嘅**自動化警示**，當測試案例跨越不穩定閾值時自動靜音（或跳過），並於恢復時解除隔離。每次轉換，無論手動定自動化，都會記錄喺測試案例嘅審核日誌中。

預設情況下，恢復會等待觸發條件不再成立，所以測試案例會保持隔離狀態，直到舊有不穩定執行離開觸發視窗。如需更快恢復，請將**恢復時機**設為**無新不穩定執行**：一旦恢復視窗（以日數或執行次數計）內沒有新不穩定執行，測試案例就會恢復；每次出現新不穩定執行都會重新開始等待；恢復後，只有新不穩定執行會再次將其隔離。


被隔離的測試處於以下兩種模式之一：

- **靜音**：測試仍會執行，但外掛程式會設定 `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> 進行比對。

### 啟用功能 {#enabling-stress-testing}

預設情況下閘門係關閉嘅。請設定模式：

- **`report`**：針對每個不穩定測試案例發出警告。執行仍會按其自身結果退出。
- **`enforce`**：不穩定測試案例會導致執行失敗，並返回與測試失敗相同嘅退出代碼。

先使用 `report` 幾星期，睇下 `enforce` 原本會攔截咩內容。


在 `settings.gradle.kts` 中宣告，或設定 `TUIST_TEST_STRESS_NEW_TESTS` 以針對每個 CI 通道變更模式：

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

與隔離不同，此閘道沒有感知 CI 的預設值。若未設定 `mode`，它不會在任何地方執行。

### 每個測試案例重新執行嘅次數 {#stress-reruns}

取決於測試案例喺第一輪執行所花嘅時間：

| 第一輪 | 重新執行次數 |
| --- | --- |
| 最多 5 秒 | 10 |
| 最多 10 秒 | 5 |
| 最多 30 秒 | 3 |
| 最多 5 分鐘 | 2 |
| 超過 5 分鐘 | 唔會重新執行 |

當達到 200 個測試案例或 10 分鐘時（以先到者為準），執行就會停止，並顯示觸發咗邊個限制。


每次重新執行都是一個嵌套的 Gradle 建構，並過濾至該單一測試案例，因此每次重新執行都會獲得全新的測試 JVM。

### 閘門未執行時 {#when-the-gate-does-not-run}

- 第一輪已經失敗。先修復該等測試。
- 專案沒有預設分支，或者 CI 尚未在其上執行任何內容。
- 超過 30% 的專案測試案例看似新增。重新執行這麼多案例會得不償失。如果專案剛開始向 Tuist 回報，或者因重新命名而一次移動大量測試，就會出現這種情況。


## Slack 通知 {#slack-notifications}

喺你嘅 Slack 整合中設定 <.localized_link href="/guides/integrations/slack#flaky-test-alerts">不穩定測試警報</.localized_link>，以便喺測試變得不穩定時收到通知。


## 查詢不穩定及隔離狀態 {#querying}

### CLI {#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 {#querying-rest-api}

同樣的資料可透過 HTTP 取得。請參閱 API 參考文件中的 [測試案例端點](https://tuist.dev/api/docs#tag/test-cases)，以查看完整的路由、篩選條件及回應欄位清單。如要變更測試案例的狀態（標記或取消標記為不穩定、靜音、跳過），請傳送 `PATCH /api/projects/{account_handle}/{project_handle}/tests/test-cases/{test_case_id}` 並附上 `is_flaky` 或 `state`。相同的變更亦可透過 `tuist test case update` 及 `update_test_case` MCP 工具進行。

