Testing in Xcode


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 支持综述

Async Testing

  • XCTestExpectation表示一个异步任务, 创建即表示任务开始执行, 需要在特定时机fulfill.
    • 早期只能通过XCTestCase的 method 创建, 且在一个 test 中, 创建后必须完成, 否则此 case 即失败.
    • 新 API 可以独立创建 expectation.
    • 通过XCTestCasewait方法检测完成情况.
  • XCTWaiter 把异步任务执行结果的检验从XCTestCase中抽离出来.
    • 可以注入 delegate, 考察wait方法的返回值来作更精细的检验.
    • 默认情况下, 不管是什么 result 都不会触发 test failure.

Attachments

  • XCTContextrunActivity方法可以在 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支持初始化单个对象, 可以独立设置 custom URLProtocol来提供 mock 数据.
    • Notification相关: 为了防止被其他 code 干扰, 官方的NotificationCenter支持初始化单个对象.
  • 如果体系本身不支持, 那么我们可以将依赖到的不可控的外部类抽象成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 后典型的流程有:

  1. 编译&测试工程. 核心命令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 的结果用于后续步骤.
  2. 解析 test 结果, 从而输出 log/自动建立 bug.
    • xcrun xcresulttool 需要输入 xcresult 文件, 解析 test result, 获取 attachment 等.
  3. 检查 code coverage
    • xcrun xccov --report: 需要输入 xcresult 文件, Xcode 11+.
    • xcrun xccov: 需要输入 xccovreport 文件.