References:
- What’s New in Testing, WWDC 2017, Session 409
- 异步 testing API 的变化
- UI Test 支持多 App test
- UI Test 的 query 性能优化 (需要 programmer 介入)
- Attachments
- What’s New in Testing, WWDC 2018, Session 403
- Code coverage 优化
- Xcode 新的 Testing 选项
- 支持并行执行 test
- Testing Tips & Tricks, WWDC 2018, Session 417
- 编写 test case 的 best practics
- Testing in Xcode, WWDC 2019, Session 413
- review
XCTest
基本功能 - 新增 Test Plans
- CI 支持综述
- review
Async Testing
XCTestExpectation
表示一个异步任务, 创建即表示任务开始执行, 需要在特定时机fulfill
.- 早期只能通过
XCTestCase
的 method 创建, 且在一个 test 中, 创建后必须完成, 否则此 case 即失败. - 新 API 可以独立创建 expectation.
- 通过
XCTestCase
的wait
方法检测完成情况.
- 早期只能通过
XCTWaiter
把异步任务执行结果的检验从XCTestCase
中抽离出来.- 可以注入 delegate, 考察
wait
方法的返回值来作更精细的检验. - 默认情况下, 不管是什么 result 都不会触发 test failure.
- 可以注入 delegate, 考察
Attachments
XCTContext
的runActivity
方法可以在 report 中产生一个 block, 用来把一些 test 示例组合到一起.- Activity 的功能之一是给 report 增加
XCTAttachment
XCTAttachment
默认清除逻辑是成功清除, 失败不清除, 此默认行为可以在scheme中更改.- 每个attachment还可以强制设置
lifetime
变更行为.
- 不使用 Xcode GUI 提取attachment, 需要用到xcresulttool这个 tool (Xcode11开始).
- 待检测的文件根目录位于 DerivedData 文件夹中, 一般为"DerivedData/$PRODUCT_NAME-xxxxxxxxxx"(非DerivedData/Build).
- 子路径为"Logs/Test", 文件后缀一定是"xcresult".
- 参考官方 release notes, 命令为
xcrun xcresulttool get --format json --path ./Example.xcresult
, 可以通过追加--id REF
输出子 object. - 一个 attachment 是 string 的例子: Root -> testsRef(–id) -> summaryRef(–id) -> attachments/payloadRef -> 目标 string data 的文件名(实际路径为 Data/data.$id, 可以用 export 命令导出)
- 官方格式非常复杂, 可以用第三方 toolxcparse, 这是其介绍文章.
Code Coverage
- 可以在 Xcode 配置.
- 要统计 coverage 的代码来自哪些 target
- 设定每个 target 中新写的 test case 是否自动 enable
- 每个 class 中的 tests 是否乱序执行
- 是否并行执行不同的 class
xcodebuild -parallel-testing-worker-count
可更改.- 除了 mac 上的 UITest 外, 都支持(simulator 或 unit test).
- 命令行 tool:
xccov
.- 处理".xccovreport"文件, Xcode 11之前.
- 含 code coverage 信息的".xcresult"文件, Xcode 11+.
编写 Unit Test 的 Best Practices
code/API 细节
- 所有 test methods 支持定义成
throws
. XCTNSPredicateExpectation
是依照轮询机制实现的, 可能较慢, 优先用其他Expectation
.XCTSkipIf
系方法可以跳过当前 case, 并且后续代码不执行, 在 result 中会标记成 skipped.XCTUnwrap
专用于 unwrap optional.XCTAssertThrowsError
专用于 catch error.
隔离 Dependency
当某一个模块我们需要单独测试, 但是该模块有关联的外部对象时, 如何编写测试 code.
- 如果外部依赖提供了 framework 层面的机制, 允许我们替换一部分逻辑, 则可以编写 code 安插到体系中/
- Network 相关: 官方的
URLSession
支持初始化单个对象, 可以独立设置 customURLProtocol
来提供 mock 数据. - Notification相关: 为了防止被其他 code 干扰, 官方的
NotificationCenter
支持初始化单个对象.
- Network 相关: 官方的
- 如果体系本身不支持, 那么我们可以将依赖到的不可控的外部类抽象成protocol.
- 配合 dependency injection, 把 protocol 类型的对象作为待测试组件初始化时的输入.
- 在生产环境 code 中, 将实际的外部类遵循这个 protocol; 而在 test 环境中编写一个临时的该 protocol 遵循者来 mock 行为.
- 当外部依赖和待测试组件之间的关系比较复杂的时候(比如有某种更深层次的依赖: 外部依赖对应的某个 delegate method 需要引用到与外部依赖同样 type 的对象, 从而导致 mock 方不仅仅需要 mock 外部依赖抽出的 protocol, 还要把这个 delegate 进一步抽象), 需要同时抽象外部依赖中多个层面的类型, 必要时候还需要依赖 type 强转.
- 当涉及到耗时较久的异步机制时, 我们可以把异步机制抽象出来. 在生产环境 code 中, 使用实际的异步流程; 在 test 环境中使用顺序流程.
在 Xcode 执行 test 的操作技巧
- Source editor 中的菱形按钮 control-click 可以 jump to report.
- 可以在 test navigator 中 command click 选择多个 case.
- 菜单 Product/Perform -> Action 中可以进行特殊的 test 操作 (例如重复刚才的 test).
Test Plans
对某个 target 执行 test 时, 可以调整不同的运行参数/选项进行多次 test.
- Xcode 基本操作支持
- scheme editor 中把 test 升级成 test plan.
- 可以在 test navigator 中 control-click, 选择执行 plan 中某个特定的 test 配置.
- 支持调整的参数主要有:
- launch arguments & environment vairables
- language
- Xcode runtime Sanitizers, diagnostics & checker (允许重新编译)
- test 选项
- 多个 scheme 可以重复引用同一个 test plan.
- 一个 scheme 可以包含多个 test plan, 其中一个为 default.
- 默认执行 test 时, 使用 default test plan, 也可以在 test navigator 中切换.
- 在使用命令行时, 可以通过
xcodebuild -showTestPlans
查看所有该 scheme 的 test plan,xcodebuild test -testPlan
选择 plan.
CI 相关
一个 project 进入 CI server 后典型的流程有:
- 编译&测试工程. 核心命令
xcodebuild
xcodebuild test
: 整体执行某个 target 的 test 动作xcodebuild build-for-testing
: 只 build 不 test, 生成".xctestrun"文件. 文件格式是 property list, 具体说明见man xcodebuild.xctestrun
命令.xcodebuild test-without-building -xctestrun xxx.xctestrun
: 单独test.- 最后2步可以分离.
-resultBundlePath xxx.xcresult
: 可以指定 test 的结果用于后续步骤.
- 解析 test 结果, 从而输出 log/自动建立 bug.
xcrun xcresulttool
需要输入 xcresult 文件, 解析 test result, 获取 attachment 等.
- 检查 code coverage
xcrun xccov --report
: 需要输入 xcresult 文件, Xcode 11+.xcrun xccov
: 需要输入 xccovreport 文件.