WWDC Quick Look 💓 By SwiftGGTeam
XCTSkip your tests

XCTSkip your tests

观看原视频

Highlight

XCTest 的 XCTSkip API(Xcode 11.4 引入)让测试在运行时条件不满足时返回显式 skip 结果,Xcode 和 CI 报告会记录跳过位置、原因和设备维度结果。

核心内容

集成测试经常依赖运行环境。一个测试可能只适合 iPad,因为功能只在 iPad 上启用;另一个测试可能调用 iOS 13.4 才有的 API;还有测试依赖一台偶尔维护的服务器。这些条件在编译期很难判断,只能等测试运行时才知道。

过去处理这种情况很尴尬。直接 return 会让测试显示为通过,报告会暗示代码已经被验证;强行失败又会把环境限制当成产品问题,浪费排查时间。XCTSkip 给 XCTest 增加了第三种结果:通过、失败、跳过。

session 的例子是一个叫 Play Garden 的小 App。它新增了 pointer interaction 测试,但 pointer interaction 只在 iOS 13.4 之后可用,App 也只在 iPad 上启用这项交互。测试方法开头先检查系统版本和设备类型;条件不满足时,测试抛出 skip,而不是伪装成通过或失败。

这个结果会出现在 Xcode 的多个入口。源码旁会显示跳过注释,Test Navigator 可以筛选 skipped tests,test report 会列出文件、行号和 reason。CI 里同一个测试跑在三台设备上时,报告可以同时展示一个 iPad 通过、其他设备跳过,这比一个含糊的绿色结果更接近真实测试覆盖。

详细内容

1. 把运行时前置条件变成测试结果

00:14)session 一开始就定义了问题:有些测试的依赖无法轻易 mock,例如设备类型、系统版本和外部服务状态。XCTSkip 让测试作者在运行时声明“这次没有执行”,并把这个状态写入测试结果。

01:20)这个 API 在 Xcode 11.4 引入。测试现在可以通过、失败,也可以被标记为 explicit skip。Xcode 用专门的 skip 图标区分这些测试,让报告说明测试套件实际验证了什么。

func testExample() throws {

    /// Example usage: skip test if device is not an iPad
    try XCTSkipUnless(UIDevice.current.userInterfaceIdiom == .pad,
              "Pointer interaction tests are for iPad only")

    // test...
}

关键点:

  • testExample() 标记为 throws,因为 skip 会通过抛出 XCTest 可识别的结果中断当前测试。
  • XCTSkipUnless 在表达式为 false 时跳过测试。
  • UIDevice.current.userInterfaceIdiom == .pad 对应 transcript 里的 iPad-only pointer interaction 场景。
  • reason 字符串会进入 Xcode 和 CI 报告,团队成员能看到跳过原因。

2. 用 guard 处理系统版本要求

02:19)Play Garden 的 pointer interaction 测试还有一个系统版本条件:pointer interaction 在 iOS 13.4 引入。旧系统上运行这条测试没有意义,因为待测 API 不存在。

05:52)session 展示了直接抛出 XCTSkip struct 的写法。它适合和 guard 搭配,用一条前置条件挡住后续测试逻辑。

func testExample() throws {

    /// Example usage: skip test if OS version is older than iOS 13.4
    guard #available(iOS 13.4, *) else {
        throw XCTSkip("Pointer interaction tests can only run on iOS 13.4+")
    }

    // test...
}

关键点:

  • #available(iOS 13.4, *) 把系统版本要求写在测试入口。
  • throw XCTSkip(...) 直接返回 skip 结果,测试报告不会把旧系统环境误报成失败。
  • reason 说明了跳过条件和最低系统版本。
  • 后续测试代码只在满足前置条件的环境中运行,断言失败才更可能代表真实产品问题。

3. 理解 XCTSkipIfXCTSkipUnless

05:25)API 层面有两个 throwing function:XCTSkipIfXCTSkipUnless。它们参数相同,差别在触发条件。

try XCTSkipUnless(UIDevice.current.userInterfaceIdiom == .pad,
                  "Pointer interaction tests are for iPad only")

关键点:

  • XCTSkipIf 在表达式为 true 时跳过。
  • XCTSkipUnless 在表达式为 false 时跳过。
  • 两者都适合写在测试方法开头,先声明运行条件,再进入真正的断言。
  • 选择哪个函数取决于条件表达式读起来是否自然。

4. 在 Xcode 和 CI 里查看 skip

03:22)当测试在旧设备上运行时,Xcode 把绿色 pass 图标换成灰色 skip 图标,并在源码中标出跳过位置和原因。

03:37)Test Navigator 会显示 skipped 状态,还能用底部按钮筛选只看跳过的测试。test report 展开后会显示文件、行号和 reason。Xcode 12 新增的 assistant 按钮可以打开 secondary editor,把报告和源代码并排展示。

04:34)CI 报告会保留设备维度。示例里同一个测试跑在三台设备上,一个 iPad 通过,其他设备跳过;展开每个设备结果后,可以看到对应的跳过位置和 reason。

try XCTSkipUnless(UIDevice.current.userInterfaceIdiom == .pad,
                  "Pointer interaction tests are for iPad only")

关键点:

  • 同一条测试可以在不同 destination 上得到不同结果。
  • skip reason 是 CI 排查材料的一部分,不需要回到本地猜测为什么没跑。
  • 通过、失败、跳过分开统计,测试覆盖情况更接近实际执行情况。
  • 这类信息适合放进 pull request 或 nightly test 的 result bundle 分析流程。

核心启发

  • 做什么:按设备能力跳过 UI 测试。 为什么值得做:session 的 iPad pointer interaction 示例说明,设备类型是运行时条件。怎么开始:在测试方法开头用 XCTSkipUnless(UIDevice.current.userInterfaceIdiom == .pad, "...") 保护只适合 iPad 的交互测试。
  • 做什么:把最低系统版本写进测试入口。 为什么值得做:旧系统缺少待测 API 时,失败结果会误导排查。怎么开始:用 guard #available(...) else { throw XCTSkip(...) } 包住依赖新 API 的测试。
  • 做什么:在 CI 报告里追踪 skipped tests。 为什么值得做:同一条测试在不同设备上可能出现 pass 和 skip,session 展示了设备维度结果。怎么开始:保留 result bundle,按 skipped filter 查看跳过数量、位置和 reason。
  • 做什么:为集成测试建立前置条件检查。 为什么值得做:外部服务器维护、硬件能力缺失或账号状态异常都属于运行时条件。怎么开始:把检查放在测试第一段,条件不满足时抛出 XCTSkip,条件满足后再执行断言。
  • 做什么:把 skip reason 写成可排查文本。 为什么值得做:Xcode 源码注释、Test Navigator、test report 和 CI 展开项都会显示 reason。怎么开始:用包含条件和行动线索的字符串,例如最低系统版本、目标设备或服务状态。

关联 Session

评论

GitHub Issues · utterances