# Bazel 不稳定测试 {#bazel-flaky-tests}

> [!IMPORTANT]
> **要求**
>
> - 通过 `tuist bazel setup` 配置<.localized_link href="/guides/features/test-insights/bazel">Bazel 测试洞察</.localized_link>。

不稳定测试是指使用相同代码多次运行时产生不同结果（通过或失败）的测试。它们会削弱你对测试套件的信任，并浪费开发人员排查误报失败的时间。Tuist 会自动检测不稳定测试，并帮助你持续跟踪它们。


## 不稳定检测的工作原理 {#how-it-works}

Tuist 利用 Bazel `test.xml` 报告中的各个用例，检测在重试之间以及持续集成中同一提交和目标选择的多次运行之间的不一致结果。本地运行仍然可见，但不会自动将测试用例标记为不稳定。

### 测试重试 {#test-retries}

重试是可选的，且独立于隔离策略。`--flaky_test_attempts=3` 可为每个测试启用最多**三次 Bazel 尝试**，而非额外三次重试。请在 `.bazelrc` 中保留现有的重试配置，或显式传递该配置：

```sh
tuist bazel test -- --flaky_test_attempts=3 //app:tests
```

必须实际执行重试，Tuist 才能观察其结果。仅凭 Bazel 目标的不稳定摘要无法确定哪个具体用例不稳定。

### 跨运行检测 {#cross-run-detection}

比较运行需要提交和分支元数据。当你的构建环境尚未发布这些元数据时，请提供它们：

```sh
tuist bazel test -- \
  --build_metadata=CI=true \
  --build_metadata=GIT_COMMIT="$GIT_COMMIT" \
  --build_metadata=GIT_BRANCH="$GIT_BRANCH" //app:tests
```

用例通过目标标签、类名（回退到套件名称）和测试名称进行识别，如<.localized_link href="/guides/features/test-insights/bazel">Bazel 测试洞察</.localized_link>中所述。

## 管理不稳定测试 {#managing-flaky-tests}

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

检测与清除通过项目上的**自动化提醒**执行。每个项目都会获得一个默认的“不稳定测试检测”自动化，其*触发器*会将测试标记为不稳定，而一旦测试在配置恢复窗口（默认**14 天**）内未再次触发，其*恢复*就会清除该标记。在**设置 → 自动化**中编辑它，可以更改恢复窗口、更改恢复操作（例如，同时将测试从隔离中释放），或完全禁用恢复，以便测试保持标记为不稳定状态，直到你手动清除它们。

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

你还可以在测试用例详情页面手动将测试标记或取消标记为不稳定。这在以下情况下很有用：
- 希望在修复已知不稳定测试时予以确认
- 由于基础设施问题导致测试被错误标记


## 隔离不稳定测试 {#quarantining}

隔离会将不稳定测试单独处理，使其在你修复期间不会阻塞 CI。默认情况下，这是一个**手动操作**：你可以在仪表板的测试用例详情页中隔离测试、解除隔离并切换模式。你也可以在**设置 → 自动化**下将其接入**自动化警报**，这样当测试超过不稳定阈值时会自动静音（或跳过），并在恢复后解除隔离。每次状态变更，无论是手动还是自动，都会记录在测试用例的审计日志中。

默认情况下，恢复会等待触发条件不再满足，因此测试会保持隔离状态，直到其旧的不稳定运行离开触发窗口。若要更快恢复，请将**恢复条件**设置为**无新的不稳定运行**：在恢复窗口（天数或运行次数）内没有新的不稳定运行时，测试即可恢复；每次出现新的不稳定运行都会重新开始等待；恢复后，只有新的不稳定运行才会再次将其隔离。


`tuist bazel test` 会在每次调用前获取隔离策略，并将其应用于底层的 `bazel test` 运行。普通的 `bazel test` 仍会报告测试结果，但不会应用隔离策略：

```sh
tuist bazel test -- //app:tests
```

`--` 之后的参数会转发给 `bazel test`。使用 `--path` 选择项目和工作目录，使用 `--bazel` 选择不同的可执行文件（例如 `bazelisk`）。命名的 Bazel 配置（例如 `--config=ci`）是可选的，工作方式相同。

### 静音 {#mute}

将**静音**作为主要的隔离工作流。被静音的用例会继续运行，其失败仍然可见，健康的兄弟用例仍会影响结果。只有当报告将每个失败目标中的所有失败都归因于静音用例时，Tuist 才会将 Bazel 的测试失败退出码转换为成功。构建错误、超时、中断的运行、缺失或不完整的报告，以及静音集合之外的失败都会保持 Bazel 的失败状态。

### 跳过 {#skip}

跳过按 Bazel 目标生效：跳过一个用例会排除其整个目标（包括健康用例），因为 Bazel 无法为每个测试框架过滤单个用例。除非你打算排除整个目标，否则优先使用**静音**。命令会打印每个被排除的目标标签，以便构建日志显示隔离策略排除了哪些内容。

如果被跳过的目标不再存在，Tuist 会在警告中指出该目标，并在不包含该排除项的情况下运行。将过时的用例返回到**启用**状态以永久移除其策略。如果没有剩余的测试目标，Bazel 将保持其正常的“未找到测试”退出状态。

### 为何隔离的测试可能显示为通过 {#quarantined-passing}

- **静音测试**仍会执行。失败的静音测试会在测试用例运行中被记录为**失败**并标记为不稳定；单个测试的状态不会被改写。被覆盖的是**整体测试运行**：如果运行中所有失败的测试用例都被静音，则整个运行会被报告为通过，因此静音的失败不会导致 CI 中断。
- **跳过的测试**根本不会运行，因此仪表板会继续显示该测试最后一次实际执行的状态。该快照可能已有数周之久。


### 再次运行已隔离的测试 {#running-quarantined-tests-again}

若要调查或验证修复，请绕过隔离策略：

```sh
tuist bazel test --no-quarantine -- --nocache_test_results //app:tests
```

将用例返回到**启用**状态，以便在后续的正常运行中包含其目标。跳过的用例在被显式包含之前不会产生新的执行，因此基于成功新运行的自动化需要在恢复它们之前进行一次验证运行。

### 限制 {#limitations}

- 显式的 `--build_event_json_file` 或 `--target_pattern_file` 不能与活跃的隔离策略结合使用。请直接传递目标模式。
- 如果 Tuist 无法获取隔离策略，命令会在运行测试之前停止。它永远不会应用部分策略。当你有意希望在不使用隔离策略的情况下运行时，请使用 `--no-quarantine`。
- 与较早的基于套件的标识下记录的静音相匹配的失败仍然会阻塞，并且命令会指出需要重新应用的策略。

请参阅 Bazel 的[测试过滤器](https://bazel.build/docs/user-manual#test-filter)、[命令行选项](https://bazel.build/reference/command-line-reference)和[构建事件描述](https://bazel.build/remote/bep-glossary)以了解底层行为。

## 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 工具完成。

