跳到内容

Xcode 不稳定测试

要求
  • 已配置Xcode 测试洞察。Tuist 会根据每次测试运行上传的测试洞察事件来检测不稳定测试。

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

不稳定测试页面

不稳定检测的工作原理#

Tuist 通过两种方式检测不稳定测试:

测试重试#

当你使用重试功能运行测试时,Tuist 会分析每次尝试的结果。如果某个测试在某些尝试中失败而在其他尝试中通过,它会被标记为不稳定。

通过 tuist xcodebuild test 传递 -retry-tests-on-failure 或 -test-iterations:

bash
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 相同:

bash
tuist xcodebuild test -scheme MyScheme

跳过的测试会以-skip-testing <identifier>的形式附加到你的 xcodebuild 调用中,并且永远不会启动。静音测试正常运行;如果它们失败,失败会在生成的构建状态中被屏蔽。

绕过隔离#

tuist xcodebuild test接受--skip-quarantine以运行所有内容,包括静音和跳过的测试:

bash
tuist xcodebuild test --skip-quarantine -scheme MyScheme

直接调用 xcodebuild#

如果你无法通过tuist xcodebuild test,请使用tuist test case list自行将被隔离的测试扩展为-skip-testing参数:

bash
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 通道调整模式:

bash
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 跟踪的关于测试用例的所有信息:

bash
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模型上下文协议工具实现。