同一提交在开发者电脑上跑完全部测试,进入云端 Mac 后却只执行了一部分,最常见的原因并不是测试代码,而是 .xctestplan 已经悄悄漂移:有人在 Xcode 里临时跳过用例、关闭诊断项,或把只适合本机的环境变量保存进计划文件。解决办法是把 XCTestPlan 当作构建输入,而不是图形界面的附属设置。
先固定测试契约的边界
一个可复现的测试入口至少包含共享 Scheme、XCTestPlan、执行目标和命令行参数。先确认 Scheme 与计划确实能被命令行发现:
WORKSPACE="${WORKSPACE:-App.xcworkspace}"
SCHEME="${SCHEME:-App}"
xcodebuild \
-workspace "$WORKSPACE" \
-scheme "$SCHEME" \
-showTestPlans
如果输出中没有预期计划,先检查 Scheme 是否设为 Shared、计划文件是否加入仓库,以及 Scheme 的 Test 动作是否引用了它。不要用 CI 参数临时补一个不存在的计划,这会让本地入口与流水线继续分叉。
计划中应明确评审以下内容:
| 项目 | 需要确认的事实 | 常见漂移 |
|---|---|---|
| Test Targets | 哪些单元测试和 UI 测试参与执行 | 新目标未加入 |
| Selected Tests | 是否只运行指定用例 | 调试选择被提交 |
| Skipped Tests | 每个跳过项是否有负责人和期限 | 失败用例被永久隐藏 |
| Configurations | 语言、地区和启动参数 | 本机配置覆盖默认配置 |
| Diagnostics | 崩溃、线程和性能诊断策略 | 为提速而意外关闭 |
| Parallelization | 哪些目标允许并行 | 共享状态测试互相干扰 |
跳过测试不是修复。任何新增的 skipped test 都应当像代码变更一样进入评审,并说明恢复条件。
生成可读的规范化差异
XCTestPlan 是结构化文件,直接审查原始文本容易被字段顺序和自动生成标识干扰。可以在仓库中保存一份规范化基线,只移除不影响执行语义的配置标识:
PLAN="App.xctestplan"
CURRENT=".ci/xctestplan.current.json"
BASELINE=".ci/xctestplan.baseline.json"
mkdir -p .ci
plutil -convert json -o - "$PLAN" |
jq -S 'del(.configurations[]?.id)' > "$CURRENT"
diff -u "$BASELINE" "$CURRENT"
首次接入时,人工核对 $CURRENT 后将它复制为基线并提交。后续流水线只生成当前文件并执行 diff。目标、跳过项、环境变量、参数和诊断选项都必须保留;不要为了得到“干净差异”继续删除业务字段。
规范化脚本也属于测试基础设施。脚本改动与计划改动应放在同一个合并请求中展示,否则一次过滤规则放宽就可能让后续漂移全部失去可见性。
拦截敏感值与主机依赖
计划文件适合保存变量名,不适合保存令牌、密码、私钥内容或开发者目录。先递归提取启用的环境变量:
plutil -convert json -o - App.xctestplan |
jq -r '
.. |
objects |
.environmentVariableEntries? // empty |
.[]? |
select(.enabled == true) |
[.key, .value] |
@tsv
'
检查输出时重点拦截三类内容:看起来像密钥的长字符串、/Users/某人/ 形式的绝对路径,以及只在交互式 Shell 中存在的工具路径。真实敏感值应由 CI 的受控环境在运行时注入,测试代码只读取变量名,并在缺失时给出明确错误。
避免配置覆盖优先级失控
XCTestPlan、Scheme、xcodebuild 参数和测试代码都能设置启动参数。建议规定单一优先级:计划保存稳定默认值,CI 只注入敏感值与本次运行标识,测试代码不修改进程级配置。若流水线使用 -only-testing 或 -skip-testing,应把参数写入可审查脚本,不能藏在任务面板的临时输入框中。
用固定目标执行并保存证据
先通过 xcrun simctl list devices available 选择当前节点已安装的设备,并把 UDID 作为流水线输入。相比模糊指定“最新系统”,固定设备与运行时更容易解释差异。
DEVICE_UDID="${DEVICE_UDID:?Set DEVICE_UDID from simctl}"
RESULT_PATH="${RESULT_PATH:-artifacts/CI.xcresult}"
rm -rf "$RESULT_PATH"
mkdir -p "$(dirname "$RESULT_PATH")"
xcodebuild test \
-workspace "$WORKSPACE" \
-scheme "$SCHEME" \
-testPlan CI \
-destination "platform=iOS Simulator,id=$DEVICE_UDID" \
-resultBundlePath "$RESULT_PATH"
无论成功还是失败,都应保存 .xcresult、完整命令、提交标识、Xcode 版本和所选设备 UDID。不要只截取最后几十行日志;测试未执行、进程崩溃和断言失败需要不同证据,结果包能保留测试层级、附件与诊断信息。
并行执行要从保守值开始。依赖同一数据库、固定端口或共享文件的测试目标不应直接开启并行。先把状态隔离做完整,再逐个允许并行,而不是用重试掩盖竞争条件。
把审计变成合并门禁
最终门禁应按固定顺序运行:验证 Scheme 能发现计划、生成规范化文件、比较基线、扫描敏感值与绝对路径、核对跳过列表,最后才执行测试。这样配置错误会在启动模拟器之前失败,节省排队和诊断时间。
提交前可用以下清单复核:
- Scheme 已共享,计划文件已进入版本控制。
- 新增测试目标已加入正确计划。
- skipped tests 均有原因、负责人和恢复条件。
- 计划中没有真实敏感值及个人目录。
- CI 没有隐藏的测试范围覆盖参数。
- 设备 UDID、Xcode 版本与结果包均被记录。
- 规范化规则没有删除执行语义字段。
- 失败运行仍会归档
.xcresult。
当 XCTestPlan 的每次变化都能在代码评审中被读懂,“本机全绿、云端漏测”就不再是偶发谜题,而会变成一条能在执行前被阻止的配置差异。
常见问题
为什么不能只依赖 Xcode Scheme 里的测试设置?
Scheme 只能说明入口,实际测试目标、配置、环境变量、语言地区和跳过列表可能保存在 XCTestPlan 中。CI 应同时固定共享 Scheme、计划文件和执行命令。
XCTestPlan 中可以保存访问令牌吗?
不应保存真实令牌。计划文件只保留变量名或无敏感性的默认值,真实值应由受控的 CI 环境在运行时注入,并避免写入测试附件和日志。
规范化 XCTestPlan 会不会漏掉重要变更?
只应移除随机生成且不影响执行语义的标识,并保留测试目标、选中与跳过项、配置名称、参数和诊断选项。规范化规则本身也必须进入代码审查。
为开发流水线配置固定的云端 Mac
MangoVM 提供 M4 与 M4 Pro 两档 Apple Silicon 物理节点。选择租期、区域和存储附加项后,即可核对完整订单明细。