Xcode 不稳定测试
- 已配置Xcode 测试洞察。Tuist 会根据每次测试运行上传的测试洞察事件来检测不稳定测试。
不稳定测试是指在使用相同代码多次运行时产生不同结果(通过或失败)的测试。它们会削弱你对测试套件的信任,并浪费开发人员排查误报失败的时间。Tuist 会自动检测不稳定测试,并帮助你随时间跟踪它们。

不稳定检测的工作原理#
Tuist 通过两种方式检测不稳定测试:
测试重试#
当你使用重试功能运行测试时,Tuist 会分析每次尝试的结果。如果某个测试在某些尝试中失败而在其他尝试中通过,它会被标记为不稳定。
通过 tuist xcodebuild test 传递 -retry-tests-on-failure 或 -test-iterations:
tuist xcodebuild test \
-scheme MyScheme \
-retry-tests-on-failure \
-test-iterations 3

跨运行检测#
即使没有测试重试,Tuist 也可以通过比较同一提交、同一 scheme 和同一报告目标(设备名称、平台和操作系统版本)下不同 CI 运行的结果来检测不稳定测试。如果一个测试在一个匹配的 CI 运行中通过而在另一个中失败,则失败的执行会被标记为不稳定。无论哪个结果先到达,此机制都有效。
同一个测试在不同的 scheme 或目标上可能表现出持续不同的行为。例如,在 iOS 26 上通过而在 iOS 27 上失败并不构成不稳定性。来自不同 scheme、设备、平台或操作系统版本的结果不会互相标记为不稳定。
无法归属到具体目标的结果会与其他未归属的结果进行比较,并与已知目标的结果分开处理。这包括缺少或不完整的目标元数据的报告,以及包含多个不同目标的报告,因为单个测试结果尚无法归属到具体目标。该组内环境之间的差异仍可能导致错误的不稳定分类。基于重试的检测仍然适用。现有的历史不稳定分类保持不变。
这对于捕获那些失败频率不足以被重试捕获、但仍会导致间歇性 CI 失败的不稳定测试特别有用。
管理不稳定测试#
自动清除#
检测和清除通过项目上的自动化警报运行。每个项目都有一个默认的“不稳定测试检测”自动化,其触发器会将测试标记为不稳定,而其恢复会在测试经过配置的恢复窗口(默认14 天)且未重新触发后清除标记。在设置 → 自动化下编辑它,以更改恢复窗口、更换恢复操作(例如同时解除隔离),或完全禁用恢复,使测试保持标记为不稳定状态,直到你手动清除。
手动管理#
你还可以从测试用例详情页面手动将测试标记为不稳定或取消标记。这在以下情况下很有用:
- 你想在修复已知不稳定测试时予以确认
- 测试因基础设施问题被错误标记
隔离不稳定测试#
隔离会将不稳定测试孤立起来,使其在你修复期间不会阻塞 CI。默认情况下,这是一个手动操作——你可以从仪表板中的测试用例详情页面进行隔离、解除隔离和切换模式——但你也可以将其接入设置 → 自动化下的自动化警报,以便当测试超过不稳定性阈值时自动静音(或跳过),并在恢复时解除隔离。每次转换,无论是手动还是自动,都会记录在测试用例的审计日志中。
默认情况下,恢复会等待直到触发条件不再成立,因此测试会保持隔离状态,直到其旧的不稳定运行离开触发窗口。为了更快恢复,将恢复条件设置为无新的不稳定运行:一旦恢复窗口(天数或运行次数)过去且没有新的不稳定运行,测试就会恢复;每次新的不稳定运行都会重新启动等待;恢复后,只有新的不稳定运行才会再次将其隔离。
被隔离的测试处于以下两种模式之一:
- 静音:测试仍然运行,但
tuist xcodebuild test会屏蔽失败。失败仍然会馈送给不稳定测试检测器,因此你可以在不破坏构建的情况下继续观察测试。选择此模式用于你正在积极调查的测试。 - 跳过:xcodebuild 会收到
-skip-testing <identifier>,因此测试永远不会启动。它不会产生新结果,并从不稳定测试仪表板中消失,直到你重新启用它。当测试损坏、缓慢或不稳定性持续存在以至于运行它只是浪费 CI 分钟数时,选择此模式。
为什么被隔离的测试可能显示为通过#
- 静音测试仍然执行。失败的静音测试会在测试用例运行中被记录为失败并标记为不稳定——每个测试的状态不会被重写。被覆盖的是整体测试运行:如果运行中每个失败的测试用例都被静音,则整个运行会被报告为通过,因此静音失败不会破坏 CI。
- 跳过的测试根本不会运行,因此仪表板会继续显示该测试最后一次实际执行的状态——该快照可能已是数周前的数据。
运行测试#
tuist xcodebuild test是一个透传包装器,会自动尊重这两种模式。使用它的方式与调用 xcodebuild 相同:
tuist xcodebuild test -scheme MyScheme
跳过的测试会以-skip-testing <identifier>的形式附加到你的 xcodebuild 调用中,并且永远不会启动。静音测试正常运行;如果它们失败,失败会在生成的构建状态中被屏蔽。
绕过隔离#
tuist xcodebuild test接受--skip-quarantine以运行所有内容,包括静音和跳过的测试:
tuist xcodebuild test --skip-quarantine -scheme MyScheme
直接调用 xcodebuild#
如果你无法通过tuist xcodebuild test,请使用tuist test case list自行将被隔离的测试扩展为-skip-testing参数:
xcodebuild test \
-scheme MyScheme \
$(tuist test case list --quarantined --skip-testing)
这是在tuist xcodebuild test之外的安全默认行为:静音测试的失败屏蔽仅在你通过该命令运行时发生,因此跳过这两种模式可以避免虚假的 CI 失败。如果你需要更精细的控制,请改用tuist xcodebuild test。
压力测试新测试#
压力测试门控会重新运行你的分支新增的测试用例,每个用例在全新的进程中运行多次,并报告那些被证明不稳定的用例。你可以在引入该不稳定测试的拉取请求中立即看到它,而不是几周后才发现。
如果测试用例在过去 90 天内未在项目的默认分支上运行过 CI,或者未在已合并到该分支的提交上运行过,则被视为新测试用例。在使用合并队列时,一旦队列的提交合并,队列运行过的测试用例即被识别;如果队列丢弃了该测试用例,它仍保持为新测试用例。提交通过 Tuist 在 CI 中收集的Git 历史进行匹配。
启用方法#
该门控默认关闭。设置一种模式:
report:对每个不稳定测试用例发出警告。运行仍按其自身结果退出。enforce:不稳定测试用例会导致运行失败,退出代码与失败的测试相同。
先使用report几周,以了解enforce会拦截哪些内容。
将其作为参数传递到透传参数之前,或设置TUIST_TEST_STRESS_NEW_TESTS以便为每个 CI 通道调整模式:
tuist xcodebuild test --stress-new-tests report -scheme MyScheme
每个测试重新运行的次数#
这取决于测试用例在首次运行时花费的时间:
| 首次运行 | 重新运行次数 |
|---|---|
| 最多 5 秒 | 10 |
| 最多 10 秒 | 5 |
| 最多 30 秒 | 3 |
| 最多 5 分钟 | 2 |
| 超过 5 分钟 | 不重新运行 |
重新运行会复用首次运行构建的内容。当达到 200 个测试用例或 10 分钟时(以先到达者为准),运行将停止,并说明触发了哪个限制。
门控不运行的情况#
- 首次运行已经失败。请先修复这些测试。
- 项目没有默认分支,或者尚未在其上运行过任何 CI。
- 超过 30% 的项目测试用例看起来是新的。重新运行这么多用例会得不偿失。在刚刚开始向 Tuist 报告的项目中,或在重命名导致大量测试同时移动后,可能会出现这种情况。
Slack 通知#
通过在 Slack 集成中设置不稳定测试警报,可以在测试变得不稳定时立即收到通知。
查询不稳定和隔离状态#
CLI#
tuist test case命令树展示了 Tuist 跟踪的关于测试用例的所有信息:
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 参考中的测试用例端点,以获取完整的路由、过滤器和响应字段列表。要更改测试用例的状态(标记或取消标记为不稳定、静音、跳过),请使用is_flaky或state发送PATCH /api/projects/{account_handle}/{project_handle}/tests/test-cases/{test_case_id}。相同的更改也可通过tuist test case update以及update_test_case模型上下文协议工具实现。