Embrace Expected Failures in XCTest
Developer Tools 进阶 20m

在 XCTest 中拥抱预期失败

Embrace Expected Failures in XCTest

2021年6月10日

在 Apple 官方观看视频

一句话判断

XCTExpectFailure 让你终于不用再跳过(skip)那些已知失败的测试了——失败会被记录但不会阻塞 CI,等 Bug 修好后测试自动从”预期失败”变为”意外通过”。

这场 Session 讲了什么

Session 介绍了 Xcode 13 中 XCTest 的新特性 XCTExpectFailure。这个 API 解决了一个老问题:你有一个已知 Bug(比如后端接口还没上线),对应的测试目前会失败。之前的做法要么注释掉测试、要么用 XCTSkip 跳过、要么让它失败然后在 CI 里忽略。这些做法各有问题——注释掉的测试经常被遗忘,跳过的测试不提供信息,放任失败的测试让 CI 结果不可信。

XCTExpectFailure 的做法是:在测试中显式声明”我预期这段断言会失败”。测试运行时如果确实失败了,记为”预期失败”(expected failure),不阻塞测试套件。如果意外通过了——说明 Bug 被修好了或测试写错了——记为”意外通过”(unexpected pass),这个测试会标记为失败,提醒你移除 XCTExpectFailure 标记。

值得深挖的点

”意外通过”是精华所在

XCTExpectFailure 最聪明的设计是”意外通过”机制。它防止了最常见的测试维护问题:你跳过一个测试,Bug 修好了,但测试一直被跳着没人发现。有了”意外通过”,当 Bug 修好后测试会从”预期失败”变成”意外通过”,在 CI 中表现为失败——这是一个信号,告诉你”嘿,这个 Bug 好了,把 XCTExpectFailure 删掉”。

粒度控制

XCTExpectFailure 可以包裹整个测试块,也可以只包裹单个断言。这让你精确控制哪些断言是预期失败的,而不是整个测试被标记。配合 XCTIssue 的新 API,你还能过滤特定类型的失败(比如只预期 XCTAssertEqual 失败,但不预期崩溃)。

代码片段

标记单个断言为预期失败

func testUserNameDisplay() throws {
    let user = User(name: "张三", avatar: nil)
    
    // 这个断言已知会失败,因为头像显示有 Bug
    XCTExpectFailure("头像加载的 Bug 还没修,rdar://12345678") {
        XCTAssertEqual(userView.avatarImage, defaultAvatar)
    }
    
    // 其他断言正常执行
    XCTAssertEqual(userView.nameLabel.text, "张三")
}

标记整个测试为预期失败

// 整个测试都被标记
func testNewFeature() throws {
    XCTExpectFailure("新功能的后端接口还没上线")
    
    let result = try apiClient.fetchNewFeatureData()
    // 这些断言如果在预期失败的上下文中失败,不会阻塞 CI
    XCTAssertEqual(result.count, 10)
    XCTAssertTrue(result.isComplete)
}

严格模式和宽松模式

// 默认严格模式:如果测试意外通过,标记为失败
XCTExpectFailure("这个 Bug 应该还没修好", strict: true) {
    // 如果这里通过了,测试会失败
    // 提醒你移除 XCTExpectFailure
}

// 宽松模式:如果测试意外通过,只记录不阻塞
XCTExpectFailure("可能修好了也可能没有", strict: false) {
    // 意外通过只产生警告
    // 适合不确定是否已修好的场景
}

最佳实践

日常使用:XCTExpectFailure 中写明 Bug 编号(如 rdar 编号或 JIRA ticket)。这样半年后你还能知道这个预期失败对应哪个问题。当 CI 报告”意外通过”时,确认 Bug 确实修好了再移除标记。

CI 配置: 预期失败的测试在 Xcode 的测试导航器中用黄色图标显示,意外通过的用红色显示。CI 中应该把”意外通过”视为真正的失败——它是你的安全网,确保 Bug 修复后测试回归正常。

还有什么值得关注

  • XCTSkip 在 Xcode 13 中也做了增强,支持跳过单个断言而非整个测试。
  • 测试报告现在可以按”预期失败”和”意外通过”分类筛选。
  • XCTExpectFailure 支持自定义匹配条件,只预期特定类型的断言失败。
WWDC 2021