在云端 Mac 上建立 iOS 启动性能回归门禁

在云端 Mac 上建立 iOS 启动性能回归门禁

一项首页重构合并后,功能测试全部通过,用户却开始反馈首次打开变慢。启动性能最容易在日常开发中被忽略:新增数据库迁移、同步读取配置、初始化分析模块或加载大图,都可能把耗时悄悄塞进主线程。与其等人工体验发现问题,不如在固定的云端 Mac 上把冷启动测量做成合并门禁。

先定义可比较的启动场景

不要把“点开应用”当成完整测试定义。至少要固定构建配置、Simulator 机型、系统运行时、应用初始数据和启动参数。测试目标也应保持单一,例如只测从进程创建到首个可交互界面出现,不把登录请求或测试数据下载混入结果。

建议单独建立 Performance.xctestplan,只启用启动性能用例,并关闭随机执行。测试环境通过启动参数进入稳定页面:

final class LaunchPerformanceTests: XCTestCase {
    override func setUp() {
        continueAfterFailure = false
    }

    func testColdLaunch() {
        let app = XCUIApplication()
        app.launchArguments = ["-uiTesting", "-resetLocalState"]

        let options = XCTMeasureOptions()
        options.iterationCount = 8

        measure(
            metrics: [XCTApplicationLaunchMetric(waitUntilResponsive: true)],
            options: options
        ) {
            app.launch()
        }
    }
}

-resetLocalState 应由应用的测试入口处理,只清理可重建的本地状态。不要在正式代码路径中根据该参数跳过真正需要测量的初始化,否则得到的是“测试专用启动速度”。

固定云端 Mac 与 Simulator 状态

性能门禁需要独享物理机,而不是与未知任务争用 CPU 和磁盘的共享环境。每次执行前先确认当前 Xcode 路径、可用 Simulator 和磁盘余量:

set -euo pipefail

xcode-select -p
xcodebuild -version
xcrun simctl list devices available
df -h "$HOME"

在测试计划中选定一个已安装的 Simulator。不要让脚本自动挑选“第一个可用设备”,因为镜像变化会使历史数据失去可比性。门禁运行期间还应避免并行归档、依赖更新和大规模索引任务。

性能数据首先是环境数据,其次才是代码数据。无法说明运行节点、构建配置与 Simulator 状态的数字,不适合直接用于阻断合并。

首次建立基线时,应在同一提交上重复执行。若离散程度明显偏大,先排查后台任务、磁盘压力和测试数据重置逻辑,而不是立即放宽阈值。

用 xcodebuild 执行单一门禁

命令行任务只运行目标用例,并把结果包保存为流水线产物。设备名称需替换为测试计划实际固定的型号:

set -o pipefail
mkdir -p Artifacts

xcodebuild test \
  -workspace App.xcworkspace \
  -scheme App \
  -testPlan Performance \
  -destination 'platform=iOS Simulator,name=iPhone 16' \
  -only-testing:AppUITests/LaunchPerformanceTests/testColdLaunch \
  -resultBundlePath Artifacts/Launch.xcresult \
  | tee Artifacts/launch-test.log

set -o pipefail 很关键,否则 xcodebuild 失败后,管道末端的 tee 仍可能让任务返回成功。Launch.xcresult 与文本日志都应保留:前者用于查看每次测量和测试附件,后者便于快速定位编译或启动阶段错误。

检查项 固定方式 变化后的处理
Xcode 记录 xcodebuild -version 先重建候选基线
Simulator 固定机型与运行时 不与旧基线直接比较
构建配置 使用同一测试计划 拒绝临时参数覆盖
测试数据 启动参数执行确定性重置 检查重置是否完整
迭代次数 门禁统一使用 8 次 不按单次结果判定

建立基线而不是追逐单次数字

在 Xcode 的测试报告中为指定测试建立性能基线,并把生成的共享基线文件纳入版本控制。基线变更要像依赖锁文件一样接受评审:提交说明应写清变化来源、验证提交和预期影响,不能只写“更新性能数据”。

阈值需要区分提醒与阻断。可以把小幅波动留作趋势观察,把连续复跑仍超过容忍范围的变化设为失败。真正的判断依据不是某一次最快成绩,而是同一环境下多次迭代是否稳定偏离已审核基线。

以下情况不应直接更新基线:

失败后保留现场并定位责任路径

门禁失败时,先在同一云端 Mac、同一提交上复跑一次。若结果可复现,再按启动阶段拆分:进程入口、依赖注入、数据库打开、首屏模型构建、资源解码和首帧渲染。可以在这些边界加入统一的 os_signpost,让后续分析看到耗时落在哪一段,而不是凭提交规模猜测。

排查顺序应从最近变更开始,但不要只盯业务代码。构建配置变化、调试诊断选项、资源体积增长和测试夹具也会改变启动路径。修复后保留失败与通过两份 xcresult,并在合并请求中记录对照提交。

一套可靠门禁的价值不在于给启动时间贴上绝对标签,而在于让同一场景持续可比。固定环境、版本化基线、保存证据,再把基线更新纳入评审,启动性能才会从主观感受变成可追踪的工程约束。

常见问题

启动性能测试应该运行多少次?

同一提交至少执行 5 次,门禁任务建议设置 8 次或更多迭代,并保持模拟器型号、系统运行时、构建配置和后台进程一致。

性能门禁失败后应立即放宽阈值吗?

不应立即调整。先在同一节点复跑,检查模拟器状态、日志和代码路径;只有确认产品行为发生了有意变化,才重新建立并评审基线。

独享物理节点

用一台按项目启停的云端 Mac 复现工作流

从三档 Apple Silicon 配置中选择合适资源,按天、周、月或季租用。设备为独享物理机、非虚拟机,实际可用状态以控制台实时返回为准。

选择配置并下单