云端 Mac 的 Xcode 测试计划一致性门禁

云端 Mac 的 Xcode 测试计划一致性门禁

团队把单元测试、接口测试和 UI 测试拆进多个 Xcode Test Plan 后,最常见的问题不是测试失败,而是测试根本没按预期执行:有人在本地 Scheme 里切换了默认计划,有人临时跳过一个用例后提交文件,还有人修改环境变量却没同步流水线。云端 Mac 会忠实执行仓库里的配置,因此第一步不是增加重试,而是把测试计划本身当作需要审查的代码。

先固定唯一可追踪的入口

Scheme 必须是共享文件,路径通常为 App.xcodeproj/xcshareddata/xcschemes/App-CI.xcscheme。不要依赖用户目录下的 xcuserdata,那里的配置不会稳定进入版本库。

在 Scheme 的 Test Action 中引用仓库内的 App-CI.xctestplan,再让流水线显式传入计划名。构建节点开始工作时先检查计划是否可见:

set -euo pipefail

xcodebuild \
  -workspace App.xcworkspace \
  -scheme App-CI \
  -showTestPlans

xcodebuild \
  -workspace App.xcworkspace \
  -scheme App-CI \
  -testPlan App-CI \
  -destination 'platform=iOS Simulator,name=iPhone 16' \
  test

-testPlan 不能省略。否则 Scheme 默认值一旦变化,命令仍会成功,但执行的测试集合可能已经不同。

把计划文件拆成可核对的契约

.xctestplan 是 JSON 文件,适合做静态检查。门禁至少应覆盖四类字段:

检查项 应有规则 失败含义
configurations 必须包含 CI 配置 流水线入口被删除或改名
testTargets 必须包含约定目标 某组测试未被执行
skippedTests 只能命中允许清单 新增了未解释的跳过项
environmentVariableEntries 禁止敏感值,关键键必须存在 环境发生漂移或信息进入仓库

不要直接比较整份 JSON 的文本顺序。Xcode 可能重排数组或补充字段,逐字比较会制造无意义失败。应读取结构,只校验团队真正依赖的约束。

测试计划门禁的目标不是阻止任何修改,而是让“少跑了什么、改了什么环境”在合并前变得可见。

用脚本拦住目标遗漏与跳过项

下面的脚本检查 CI 配置、必需测试目标及未登记的跳过项。允许清单应保持很短,并在代码审查中要求说明移除条件。

#!/usr/bin/env python3
import json
import sys
from pathlib import Path

plan = json.loads(Path("App-CI.xctestplan").read_text())
required_targets = {"AppTests", "AppIntegrationTests"}
allowed_skips = {
    "AppIntegrationTests/testTemporaryServerResponse"
}

config_names = {item["name"] for item in plan.get("configurations", [])}
if "CI" not in config_names:
    sys.exit("Missing CI test configuration")

targets = plan.get("testTargets", [])
target_names = {
    item.get("target", {}).get("name")
    for item in targets
}
missing = required_targets - target_names
if missing:
    sys.exit(f"Missing test targets: {sorted(missing)}")

actual_skips = {
    test
    for item in targets
    for test in item.get("skippedTests", [])
}
unexpected = actual_skips - allowed_skips
if unexpected:
    sys.exit(f"Unapproved skipped tests: {sorted(unexpected)}")

将脚本放在测试命令之前执行。它失败时不应自动改写计划文件,因为自动修复可能掩盖开发者有意进行的配置变更。

为允许清单设置退出条件

允许清单不能成为永久垃圾桶。每个条目至少对应一个缺陷记录、负责人和删除条件。若团队不希望再维护第二份结构化文件,可以在代码审查模板中强制填写这些信息,并定期运行脚本输出当前清单。

分离稳定变量与流水线秘密

测试计划适合保存不会随节点变化的开关,例如 UITEST_MODE=1、固定语言或模拟服务模式。访问令牌、私钥和一次性凭据不应写入计划文件,也不应作为 Scheme 的明文参数。

云端 Mac 的流水线可以在执行前注入秘密,并让测试进程读取环境变量。审计脚本则只验证键是否存在、值是否来自允许的固定集合。这样既能保持计划可复现,也不会把敏感信息混入仓库。

还要留意变量展开目标。若计划引用了已经改名的 Target,Xcode 界面可能仍能打开文件,但运行时变量解析会偏离预期。工程重命名时,应把计划检查与 xcodebuild -list 一起执行。

让失败留下足够证据

门禁通过后再运行完整测试,并把结果包写到固定目录:

rm -rf artifacts/App-CI.xcresult

xcodebuild \
  -workspace App.xcworkspace \
  -scheme App-CI \
  -testPlan App-CI \
  -destination 'platform=iOS Simulator,name=iPhone 16' \
  -resultBundlePath artifacts/App-CI.xcresult \
  test

失败时保留三份信息:当前 .xctestplan、实际执行命令和 .xcresult。只保存控制台尾部日志不足以判断是测试逻辑失败、目标未加载,还是计划配置改变。

在 ZoneMini 上运行固定测试节点时,也应在每次任务开始前重新执行静态审计,而不是假设长期运行的环境天然一致。共享 Scheme 确定入口,测试计划描述集合,审计脚本约束变化,结果包保存证据;四层同时存在,才能让“测试通过”具有稳定含义。

常见问题

为什么不能只在 CI 中固定一条 xcodebuild 命令?

命令只能指定 Scheme 与测试计划名称,无法阻止计划文件内部的测试目标、跳过项和环境变量被修改。应同时审计版本库中的 .xctestplan 与共享 Scheme。

测试计划中的 skippedTests 是否应该全部禁止?

不必一刀切。长期跳过项应直接修复或移除,确需临时跳过时应进入带负责人、原因和截止条件的允许清单,未登记的新增跳过项直接阻断合并。

如何避免开发机与云端 Mac 选择不同的测试计划?

将 Scheme 放入 xcshareddata/xcschemes,显式引用版本库内的 .xctestplan,并在 CI 命令中固定 -testPlan 参数;流水线开始时再用 -showTestPlans 验证计划确实可见。

DEDICATED BUILD SLOT

为持续构建任务选择独享云端 Mac

核对 M4、内存、存储、节点和计费周期,再把固定构建环境接入现有流水线。

选择配置并订购