
摘要 :一个失败的测试报告,如果只写着"某某参数错误",那和一张"失物招领"有什么区别?Swift Testing 新增的 Attachments(附件)功能(ST-0009),就像是给失败的测试现场,打上了一个**"现场取证包"**,直接将调试日志和关键数据附着在报告上,让 Bug 无所遁形。
0️⃣ 🐼 序章:万劫谷的"失灵"现场
万劫谷,一个充满了陷阱和奇门遁甲的虚拟测试环境。
大熊猫侯佩正在谷中寻找他那四根宝贝竹笋的踪迹(它们现在安全地储存在 InlineArray 里),一路上他习惯性地摸了摸头顶,确认自己的黑毛依然茂密,头绝对不秃。
他身边站着一个活泼可爱的绿衫少女,正是钟灵 。钟灵的特点是天真烂漫,喜欢饲养各种"小宠物",尤其是她那条能放出电流的"雷电蟒"(现在是她编写的 Character Struct 数据模型)。

"侯大哥,你看!"钟灵指着屏幕上的测试报告,气得直跺脚,"我的测试又失败了!它明明应该生成一个名为 Rem 的角色,结果生成了 Ram!报告上只写了预期值和实际值,可是这个失败的 Ram 角色内部状态到底是什么?它的 UUID 是多少?我完全不知道!"
侯佩叹了口气:"这就是测试的黑箱困境 。失败的报告,就像是你看到一只断了腿的兔子,但不知道它是在哪条路上、被谁咬伤的。我们得找到现场遗留的物证。"
在本次大冒险中,您将学到如下内容:
- 0️⃣ 🐼 序章:万劫谷的"失灵"现场
- 1️⃣ 📦 驯服数据:Attachable 协议的契约
- 2️⃣ 📝 现场取证:Attachment.record() 的铁律
- 3️⃣ 🚧 侠客的遗憾:现阶段的不足
- 4️⃣ 🐼 尾声:条件判断的"外功"与"内力"
钟灵急道:"对!我要把我的'雷电蟒'(数据模型)的全部信息,直接打包塞进这个失败报告里!(Swift Testing: Attachments)"

1️⃣ 📦 驯服数据:Attachable 协议的契约
要让数据能够被 Swift Testing 系统识别并打包,它必须遵守新的"江湖规矩":Attachable 协议。
侯佩指导钟灵,将她那可爱的"雷电蟒"数据模型进行武装升级:
swift
import Foundation
import Testing
// 钟灵的角色结构体,它就是那条"雷电蟒"
struct Character: Codable, Attachable {
var id = UUID() // 关键的内部状态,比如角色的唯一标识符
var name: String
}

侯佩解释道:"Attachable 协议就像是你给你的宠物签订了一份'随行契约'。只要有了这个契约,系统就知道在关键时刻,应该如何'捕捉'和'打包'它。"
🔑 技术关键点:
Codable的加持 注意,这个Character结构体不仅遵循了Attachable,还遵循了Codable。对于像结构体这样的自定义数据类型,Swift Testing 会利用Codable的能力,将其实例自动编码 成Data或String格式,然后再进行附加。这样才能确保数据是有头有脸、完整地出现在报告中。
2️⃣ 📝 现场取证:Attachment.record() 的铁律

现在,钟灵只需要在她的生产代码(Production Code)中生成她的角色:
swift
// 生产代码:生成一个新角色
func makeCharacter() -> Character {
// 默认生成一个名叫 "Ram" 的角色
Character(name: "Ram")
}
然后,在测试代码中,无论测试是成功还是失败,她都要确保这个角色的所有状态,都被系统记录下来:
swift
@Test func defaultCharacterNameIsCorrect() {
let result = makeCharacter()
// 💔 测试失败断言:预期 Rem,实际 Ram
#expect(result.name == "Rem")
// 🎒 关键步骤:记录附件!
// 将整个 result 实例附着到本次测试结果中,并命名为 "Character"
Attachment.record(result, named: "Character")
}
"太神了!"钟灵惊呼道,"当这个测试运行失败时,Xcode 就会自动将这个 result 实例的 JSON 编码数据,直接显示在测试报告的旁边!我一眼就能看到这个失败的 Ram 角色的 UUID 是多少,它的内部状态是不是被某个毒药(Bug)污染了!"

侯佩点头:"这就叫 '证据确凿' 。以前你只能 望洋兴叹 ,现在你可以 一目了然。"
3️⃣ 🚧 侠客的遗憾:现阶段的不足
侯佩作为精通技术的工程师,也指出了这一功能在 Swift 6.2 版本的些许遗憾。
- 🚫 图像缺失症: "目前,Swift Testing 尚不支持附加图像(Image),"侯佩遗憾地说,"这就像你抓到了一个间谍,却不让你拍下他的照片。如果我做 SwiftUI 界面测试,失败了却不能附上截图,那会让人非常抓狂。"
- ♻️ 生命周期控制的缺席: "另一个遗憾是,它不像 XCTest 的同类功能那样,支持生命周期控制(Lifetime Controls)。"
"生命周期控制是什么?"钟灵好奇地问。

"就是如果你的测试成功了,系统可以自动删除 你附加的这些日志文件和数据。这样可以保持测试环境的轻量化。现在嘛,你成功了,这些文件还是会留在那,徒增烦恼。"
4️⃣ 🐼 尾声:条件判断的"外功"与"内力"

解决了附件问题,钟灵的测试调试效率提升了百倍。但她很快又遇到了新的困惑。
"侯大哥,我的宠物'雷电蟒'需要在不同的硬件环境(例如 M1 芯片和 Intel 芯片)上运行不同的代码。Swift Testing 有一个很方便的功能叫做 ConditionTrait,可以用来定义'只在 M1 上运行'的测试条件。"

侯佩点头:"是的,ConditionTrait 是测试的'内功',决定测试是否应该被执行。"
钟灵苦恼道:"但是,我能不能在非测试函数 (Non-test function),比如我的生产代码里,也引用和判断这个'内功'?比如,我想写一段普通的函数,判断'我现在是不是在 M1 芯片上运行?',并根据结果调整代码逻辑。"

侯佩眼中闪过一丝精光,他知道,钟灵提出的需求,已经触及到了 Swift Testing 的深层奥秘。
"钟灵姑娘,你提出了一个跨越测试与生产代码边界 的哲学问题。你需要的不是附件,而是将测试的'内功心法',转化为人人可用的'外功'招式。"

(欲知后事如何,且看下回分解:Swift Testing: Public API to evaluate ConditionTrait ------ 如何在普通函数中,运用测试框架的'条件判断'心法。)
