Highlight
Xcode 12 将 XCTest 失败封装为
XCTIssue,让失败携带类型、详细描述、底层错误、附件和调用栈,开发者能在 Issue Navigator 与测试报告里追到触发路径。
核心内容
维护测试套件最耗时间的环节,常常出现在测试已经失败之后。失败在本机或 CI 里出现时,开发者要回答四个问题:哪里失败,怎样失败,为什么失败,以及失败到底从哪一行测试代码触发。Xcode 12 把这些问题放进了同一条排查路径。
演讲里的例子来自一个叫 PlayGarden 的小项目。测试代码为了减少重复,被抽成了共享工具函数。问题也随之出现:失败标记落在 helper 里面时,测试方法本身只显示灰色标记,开发者还得顺着调用关系找上下文。
Xcode 12 的变化,是把失败、源码位置和调用栈一起呈现。Issue Navigator 能展开测试代码里的调用栈;测试报告也能显示同一组 frame,并通过 Jump 或 Assistant 按钮把报告和源码并排打开。对于来自 CI 的 result bundle,这能减少本地复现前的盲查时间。
底层 API 也被调整。过去 XCTest 记录失败时使用消息、文件路径、行号和是否为预期失败这四个离散值。Xcode 12 用 XCTIssue 包装这些数据,并加入类型、详细描述、底层错误和附件。自定义断言、失败观察、失败过滤和诊断附件,都可以围绕同一个对象完成。
详细内容
Swift 抛错测试拥有源码位置
(04:21)XCTest 支持测试函数直接 throw。当测试抛出错误时,错误会形成失败消息。Xcode 12 结合 Swift runtime、iOS 和 tvOS 13.4、macOS 10.15.4 的改进,为这类错误报告源码位置。
(05:15)同一组便利也进入了生命周期方法:setUpWithError() 在原来的 setUp() 前运行,tearDownWithError() 在原来的 tearDown() 后运行。新建测试文件模板会包含这些方法。旧方法仍可保留,常见原因是继承结构还依赖它们。
用 XCTIssue 编写自定义断言
(09:52)自定义断言可以直接创建 XCTIssue,把失败描述、附件和调用位置写进同一个对象,再交给 record(_:)。
func assertSomething(about data: Data,
file: StaticString = #filePath,
line: UInt = #line) {
// Call out to custom validation function.
if !isValid(data) {
// Create issue, declare with var for mutability.
var issue = XCTIssue(type: .assertionFailure, compactDescription: "Invalid data")
// Attach the invalid data.
issue.add(XCTAttachment(data: data))
// Capture the call site location as the point of failure.
let location = XCTSourceCodeLocation(filePath: file, lineNumber: line)
issue.sourceCodeContext = XCTSourceCodeContext(location: location)
// Record the issue.
self.record(issue)
}
}
关键点:
var issue允许后续修改XCTIssue。XCTIssue(type:compactDescription:)记录失败类型和短描述。issue.add(XCTAttachment(data: data))把失败数据附到测试报告。XCTSourceCodeLocation使用调用方传入的file和line,让报告指向调用自定义断言的位置。self.record(issue)将 issue 交给 XCTest 和 Xcode。
在测试类里观察失败
(11:12)record(_:) 是测试类里所有失败经过的入口。覆盖它可以在一个类范围内观察失败,然后继续交给 XCTest。
override func record(_ issue: XCTIssue) {
// Observe, introspect, log, etc.:
if shouldLog(issue) {
print("I just observed an issue!")
}
// Don't forget to call super!
super.record(issue)
}
关键点:
override func record(_ issue: XCTIssue)让当前测试类接管失败记录流程。shouldLog(issue)代表本地的观察或日志条件。super.record(issue)必须调用,否则 issue 不会继续进入记录链。- 如果只想观察某一个测试类的失败,这种方式比全局观察更局部。
按条件抑制失败
(11:30)覆盖 record(_:) 后,不调用 super 就会阻止 issue 被记录和上报。
override func record(_ issue: XCTIssue) {
// If you don't want to record it, just return.
if shouldSuppress(issue) {
return
}
// Otherwise pass it to super.
super.record(issue)
}
关键点:
shouldSuppress(issue)判断当前失败是否要被过滤。return会结束记录流程,Xcode 不会收到这个 issue。- 未被过滤的失败仍然要调用
super.record(issue)。 - 这类逻辑适合放在边界清晰的测试类中,避免隐藏真正需要修复的问题。
给失败追加诊断附件
(11:39)修改 issue 是覆盖 record(_:) 的常见用途。先把参数重新声明为 var,再添加附件,最后传给 super。
override func record(_ issue: XCTIssue) {
// Redeclare using var to enable mutation.
var issue = issue
// Add a simple attachment.
issue.add(XCTAttachment(string: "hello"))
// Pass it to super.
super.record(issue)
}
关键点:
var issue = issue将传入对象变成可修改副本。issue.add(XCTAttachment(string: "hello"))演示给失败添加字符串附件。- transcript 提到附件 API 支持多种类型,可用于保存自定义诊断数据。
- 修改完成后调用
super.record(issue),让增强后的 issue 出现在 Xcode 测试报告里。
调用栈让共享 helper 的失败更清楚
(08:28)共享 helper 里有多个 assertion 时,只传入一个调用位置仍然会留下歧义。XCTIssue 捕获并符号化调用栈后,测试方法可以显示灰色标记,真正失败的 helper 行显示红色标记,Issue Navigator 和测试报告都能沿 frame 跳转。
这对大型测试套件很直接。开发者可以继续抽取复用函数,不必为了定位失败而牺牲测试结构。失败报告会保留触发路径,排查时能从测试入口走到实际 assertion。
核心启发
- 为数据校验写专用断言:做什么:把重复的
isValid(data)检查封装成测试 helper。为什么值得做:XCTIssue能把失败描述、数据附件和调用位置放进同一次失败记录。怎么开始:按官方片段创建var issue = XCTIssue(type: .assertionFailure, compactDescription: ...),添加XCTAttachment(data:),再调用record(_:)。 - 给失败自动附加诊断文本:做什么:在某个测试类里覆盖
record(_:),为每次失败补充上下文。为什么值得做:测试报告会带上额外附件,CI result bundle 更容易直接阅读。怎么开始:将传入的 issue 复制为var,调用issue.add(XCTAttachment(string: ...)),最后调用super.record(issue)。 - 为少数测试类加失败日志:做什么:只观察一个测试类里的失败,不影响整个测试 bundle。为什么值得做:演讲指出
record(_:)是失败经过的入口,适合局部观察和日志。怎么开始:覆盖record(_:),在shouldLog(issue)条件下记录信息,然后继续调用super.record(issue)。 - 迁移会抛错的 setup 和 teardown:做什么:把需要抛错的准备和清理逻辑移到
setUpWithError()、tearDownWithError()。为什么值得做:Xcode 12 会为抛出的错误报告源码位置,减少手写错误处理样板。怎么开始:保留旧方法处理继承需求,新测试优先使用带Error的生命周期方法。 - 让共享测试工具继续复用:做什么:把重复 assertion 留在 helper 中,同时依赖 Xcode 12 的失败调用栈定位路径。为什么值得做:演讲展示了灰色入口标记和红色实际失败标记,能同时说明触发处和失败处。怎么开始:运行失败测试,在 Issue Navigator 或测试报告中沿 call stack 检查 frame。
关联 Session
- Write tests to fail — 讲如何设计更容易诊断的 XCTest 和 UI 测试,是本 session 推荐的前置实践。
- Get your test results faster — 关注测试计划、超时和执行时长,让失败更快回到开发者手里。
- Handle interruptions and alerts in UI tests — 处理 UI 测试里的中断和预期弹窗,减少排查失败时的噪音。
- XCTSkip your tests — 用条件跳过表达不适合运行的测试,让测试报告保留更有意义的结果。
- Eliminate animation hitches with XCTest — 展示 XCTest 在性能和动画回归检测中的应用,可与失败诊断报告一起使用。
评论
GitHub Issues · utterances