测试与代码质量

Xcode 测试计划(Test Plan)全面解析:按配置运行的方法

测试计划通过 .xctestplan 文件管理要运行的测试及其条件。本文总结仅靠 Scheme 设置为何不够、目标选择、配置拆分、覆盖率、执行顺序、重复设置以及 xcodebuild 集成。

12 分钟阅读
Xcode 测试计划(Test Plan)全面解析:按配置运行的方法 封面图

测试代码积累到一定程度后,会出现一个新问题:“这些测试应该以什么组合运行?”

全部运行时,CI(Continuous Integration,持续集成)每次要花20分钟;只运行部分测试,又得手动反复切换选项。

想分别在韩语和英语环境中运行UI测试时,也可能找不到合适的方法。

先说结论,Apple 为解决这个问题提供的就是测试计划(Test Plan)。

Xcode 11 引入的测试计划会把“要运行的测试”和“运行条件”拆分到文件中管理。

本文将一次讲清测试计划的结构、实战配置和CI集成。

如果还不熟悉测试代码,可以先阅读使用 XCTest 基础和 Given-When-Then 编写第一个测试的方法

什么是测试计划

测试计划是扩展名为.xctestplan的文件,内容是人类可读的JSON。添加到项目后,从Scheme中引用。

一个文件包含两部分内容。

关键在于它是文件。.xctestplan可以提交到Git,和团队共享相同条件,并让设置变更进入代码审查。

团队使用时,最好将计划和引用它的共享Scheme一起进行版本控制。

为什么仅靠Scheme设置不够

测试计划出现以前,所有运行条件都在Scheme的Test操作中。这种结构存在限制。

一个Scheme只有一套Test操作设置。因此要区分快速验证和夜间完整验证,就必须复制Scheme。

复制后,构建、运行和配置文件设置也会全部带过来。为了改一个测试条件,等于复制其余所有设置。

测试计划反转了这种关系。一个Scheme可以关联多个测试计划,每个计划又可以包含多个配置。

Xcode Scheme与测试计划配置层级图,分支为Smoke、Regression、Nightly计划
无需重写测试,也能随意增加运行组合

创建测试计划

根据Apple 当前的说明,Xcode会创建一个默认计划,包含Scheme构建的测试目标中的所有测试。

  1. 在Product > Scheme > Edit Test Plan中打开并保存默认计划
  2. 在Tests标签页按目标、标签、套件和函数选择运行对象
  3. 在Configurations标签页添加共享设置和所需配置

要创建更多计划,请使用Product > Test Plan > New Test Plan。旧Scheme的编辑器中可能会出现Convert to use Test Plans,这就是Xcode 11 引入的现有设置转换流程

关联多个计划后,在Product > Test Plan > Manage Test Plans中将一个设为默认(Default)。未另行指定时就使用它。

在Product > Test Plan中启用要运行的计划。此时执行Command + U,也就是Product > Test,当前计划会按每个配置运行一次。应区分默认计划和当前启用的计划。

共享设置与配置的关系

打开测试计划编辑器会看到Tests和Configurations两个标签页,核心是Configurations。

此标签页分为上下两层。

  • Shared Settings(共享设置):所有配置继承的默认值
  • 单独配置:只覆盖共享设置中必要项目的变体

可以把它理解为CSS的继承:公共条件只写一处,各配置只指定差异项。

被覆盖的项目会在编辑器中加粗,一眼就能看出“只有这里不同”。

可设置的项目很多,常用的主要有这些。

项目 决定什么
Arguments / Environment Variables 运行参数和环境变量
Application Language / Region 应用运行时的语言和地区
Code Coverage 是否收集覆盖率以及覆盖哪些目标
Execution Order 按字母顺序运行,还是每次运行随机打乱
Test Repetition Mode 测试重复运行方式
Test Timeouts 单个测试允许的最长运行时间
Runtime Sanitization Address·Thread·Undefined Behavior Sanitizer
Memory Management Malloc Scribble, Malloc Guard Edges, Zombie Objects
Automatic Screen Capture 失败时是否自动附加屏幕截图

公共设置放在共享设置中,差异放在配置中。遵守这一原则,计划再多也不会失控。

拆分配置的实战示例

最常见的是按语言配置。多语言应用经常因语言不同导致布局错乱,拆分配置后就能自动按语言重复运行同一套UI测试代码。

  • 共享设置:开启覆盖率,失败时保存截图
  • 配置A“Korean”:将Application Language设为韩语
  • 配置B“English”:将Application Language设为英语
  • 配置C“RTL Pseudolanguage”:用于验证从右向左书写的语言

运行一次该计划后,所选测试在每个配置中运行一次,结果报告也会按配置分别显示。哪个语言失败会一目了然。

内存检查也是如此。根据Apple文档,Address Sanitizer可能使用2~3倍内存,并让代码变慢2~5倍,必须考虑运行成本。

平时的配置关闭sanitizer,另设诊断配置。例如分别建立Address Sanitizer和Thread Sanitizer配置,在夜间构建中运行。

覆盖率、随机顺序与重复运行

下面介绍配置中尤其有用的三个选项。

代码覆盖率不只是打开开关,更重要的是先确定测量对象。如果把依赖库混入同一个数值,就难以看清应用代码的变化。

选择“some targets”,只指定应用目标,才能得到有意义的数值。

Execution Order中可以选择Alphabetical或Random。但不能笼统地说所有测试默认都是Alphabetical。

XCTest计划选择Random后,每次运行都会打乱顺序。另一方面,Swift Testing默认并行运行测试函数,顺序也会随机化。需要分别理解两个框架的行为。

如果改变顺序就失败,很可能依赖前一个测试留下的状态。这是发现测试间隐藏耦合的有用信号。这种独立性在优秀单元测试的FIRST原则中也很重要。

Test RepetitionXcode 13 新增的选项,可以选择以下重复方式。

模式 行为 用途
Up Until Maximum Repetitions 不论结果如何,重复到指定的最大次数 测量间歇性失败的复现率
重复到失败(-run-tests-until-failure) 一直重复到发生失败 追踪偶尔失败的测试
Retry on Failure 失败后重试到指定次数 保障不稳定UI测试在CI中的通过率

根据Xcode 13 发行说明Maximum Test Repetitions必须指定正整数。官方文档未确认“默认3次”,因此直接检查计划中保存的值才准确。

命令行中使用-test-iterations设置次数,还可以组合-run-tests-until-failure-retry-tests-on-failure。该命令行设置优先于计划中的重复设置。

Retry on Failure很方便,但应谨慎使用。靠重试通过会让不稳定测试继续存在。

可以临时开启,但最好另行调查不稳定的原因。

包含CI PIPELINE文字,以及按拉取请求、合并和夜间构建划分的测试运行流水线插图
PR轻量运行,夜间重度运行。拆分计划即可实现

在CI中使用测试计划

测试计划也可以直接在命令行中使用。先用xcodebuild -scheme MyApp -showTestPlans确认Scheme关联的计划。-testPlan传入的是计划名称,而不是文件路径。

xcodebuild test \
  -project MyApp.xcodeproj \
  -scheme MyApp \
  -testPlan Smoke \
  -destination 'platform=iOS Simulator,name=iPhone 16'

保持同一个Scheme,只切换计划名称,就能划分CI任务的运行范围。例如:

  • 每个PR(Pull Request):-testPlan Smoke — 以核心单元测试为主
  • 合并到主分支:-testPlan Regression — 全部单元测试和UI测试
  • 夜间计划任务:-testPlan Nightly — 包含sanitizer和多语言配置

还可以按配置进一步拆分。

例如Apple 当前的命令行示例--only-test-configuration只运行指定配置;相反,--skip-test-configuration会排除指定配置后运行。两个选项都使用两个连字符。

如果计划有五个语言配置,CI系统可以创建五个任务,每个任务分配一个配置。但并行分配是CI系统的工作,不是测试计划自动分配机器或缩短运行时间的功能。

xcodebuild test \
  -scheme MyApp \
  -testPlan Localization \
  --only-test-configuration Korean \
  -destination 'platform=iOS Simulator,name=iPhone 16'

常见问题

Q. 测试计划文件要提交到Git吗?

A. 如果团队要共享相同的运行条件,建议提交。但上传到Git不是Xcode的必需条件。请同时管理计划及其引用的共享Scheme。

不过JSON在多人同时修改时会产生冲突。按计划目的拆分文件可以减少冲突。

Q. 创建多个配置后,时间不会翻倍吗?

A. 是的。测试会按配置数量重复运行。

因此,多语言和sanitizer等耗时配置适合从日常计划中移出,放到夜间计划运行。

Q. 一个计划中可以同时放单元测试和UI测试吗?

A. 可以。但单元测试按秒计,UI测试按分钟计,反馈速度差异很大。

拆成快速计划和慢速计划更符合开发流程。

Q. 用Swift Testing编写的测试也能放入计划吗?

A. 可以。选定的目标同时包含XCTest和Swift Testing测试时,可在同一计划中运行。了解Swift Testing的标签与并行运行方式也有助于拆分计划。

Q. 只想排除某些测试怎么办?

A. 在Tests标签页中,可以按目标、套件、函数或参数化的单个用例取消勾选。Xcode 16 及更高版本还可以使用 Swift Testing 的 Include Tags 和 Exclude Tags 选择范围

它只记录在计划文件中,不修改代码,因此其他计划仍会正常运行。


测试计划不是用来重新编写测试的工具,而是决定已有测试运行哪些、使用什么条件的工具。

如果一直通过复制Scheme划分条件,请保存默认计划,再按目的整理计划。在CI中按任务明确指定计划和配置可以清晰界定范围,但实际节省时间取决于运行器数量和并行方式。

先在Product > Scheme > Edit Test Plan中保存默认计划,再创建一个Smoke计划。之后添加配置会容易得多。


来源与确认标准

确认基准日为2026年8月16日。菜单名称和命令行写法优先采用上述Apple官方文档当前的表述;对于文档未确认的默认重复次数或CI缩时数据,不作断言。