
摘要 :在 Swift 6.2 的并发江湖中,我们迎来了两项截然不同的新功能:一项是关于极度精妙的文本侦查术 (SE-0448 正则表达式向后查找断言),另一项则是关于面对应用崩溃时的从容不迫(ST-0008 退出测试)。大熊猫侯佩将与阿朱、阿紫这对姐妹花,共同演绎这冰火两重天的技术奥秘。
0️⃣ 🐼 序章:雁门关前的技术难题
雁门关,数据流与现实交错的虚拟战场。
大熊猫侯佩正对着一块全息屏幕发呆,屏幕上是无数条交易记录,他正努力寻找他藏匿的竹笋基金。他用手摸了摸自己的头顶,确定了头绝对没有秃之后,才稍微心安。
他身旁站着一位温柔婉约的绿衣女子,正是阿朱 。阿朱以易容术闻名江湖,擅长在纷乱的文本中寻找和伪装信息,她的心愿是天下太平,性格宽厚善良。

"侯大哥,"阿朱指着一堆交易记录说,"我想找到所有以 **金币符号 ** 结算的价格,但我只想匹配出后面的数字,而不要把那个 ``符号也匹配进去。我要用这些数字去结算账单,符号留着下次易容用。"
在本次大模型中,您将学到如下内容:
- 0️⃣ 🐼 序章:雁门关前的技术难题
- 1️⃣ 🔎 阿朱的易容术:Regex lookbehind assertions
- 2️⃣ 🧪 阿紫的毒药测试:Exit Tests 的"置之死地" (ST-0008)
- #expect(processExitsWith:) 的安全结界
- 3️⃣ 🎁 尾声:崩溃现场的"遗物"与下一章的伏笔
侯佩为难地挠了挠头:"以前的 Regex(正则表达式) ,要么就全部匹配进去,要么就得用复杂的捕获组再分离。要想实现'只看前因,不取前因',简直难如登天啊!"

1️⃣ 🔎 阿朱的易容术:Regex lookbehind assertions
阿朱的问题,正是 SE-0448 所要解决的:向后查找断言(lookbehind assertions)。
传统的正则表达式,可以轻松地实现"向前看"(Lookahead),例如 A(?=B),匹配 A,但前提是 A 后面跟着 B。

而现在,Swift 6.2 赋予了我们 "向后看" 的能力,即 (?<=A)B:匹配 B,但前提是 B 前面紧跟着 A。最关键的是,A(前置条件)不会被纳入最终的匹配结果中。
侯佩拿起代码卷轴,为阿朱演示了这招"庖丁解牛"般的绝技:
swift
let string = "Buying a jacket costs $100, and buying shoes costs $59.99."
// (?<=\$): 向后查找断言,确认当前位置前面紧跟着一个 $ 符号。
// \d+ : 匹配至少一个数字(价格的整数部分)。
// (?:\.\d{2})?: 匹配可选的小数点和小数部分(?: 是非捕获组)。
let regex = /(?<=\$)\d+(?:\.\d{2})?/
for match in string.matches(of: regex) {
// 最终输出的 match.output 只有数字,不包含 $ 符号
print(match.output)
}
// 输出:
// 100
// 59.99
"看到了吗,阿朱姑娘?"侯佩得意洋洋,"这个 (?<=$) 就是你的易容术精髓 。它帮你确认了身份(前面必须是金币),但在匹配结果中,它却完美地把自己隐藏了起来,片叶不沾身!"

阿朱喜出望外:"太妙了!这样我就可以精准地提取数据,再也不用担心多余的符号来捣乱了!"
2️⃣ 🧪 阿紫的毒药测试:Exit Tests 的"置之死地" (ST-0008)
就在侯佩和阿朱沉浸在正则表达式的精妙中时,一阵刺鼻的硫磺味突然袭来!
另一位身着紫衣的少女,阿紫 ,从烟雾中走了出来。阿紫的特点是心狠手辣,喜欢用毒,而且热衷于测试"极限"。

"姐姐,你在玩这么幼稚的游戏?"阿紫轻蔑一笑,"我的任务才刺激。我要测试我最新的**'鹤顶红'代码**,确保它能让整个应用彻底崩溃并退出!"
侯佩吓得连退三步:"你要测试崩溃?阿紫姑娘,你知道这意味着什么吗?应用崩溃,测试系统也会跟着崩溃啊!这叫一锅端!"

阿紫的测试目标,正是那些会触发 precondition() 或 fatalError() 导致进程退出的代码。
swift
struct Dice {
// 掷骰子功能
func roll(sides: Int) -> Int {
// 🚨 前提条件:骰子面数必须大于零!
// 如果 sides <= 0,程序将立即崩溃退出!
precondition(sides > 0)
return Int.random(in: 1...sides)
}
}
"以前,我们要么不能测,要么就得用各种奇技淫巧来捕获这种'致命错误'。"侯佩擦着汗说,"但现在 Swift Testing 带来了 ST-0008:Exit Tests ,让我们能优雅地'置之死地而后生'!"

#expect(processExitsWith:) 的安全结界
Swift 6.2 引入了 #expect(processExitsWith:),它就像是一个安全结界,允许我们在隔离的子进程 中执行可能导致崩溃的代码,然后捕获并验证这个退出行为。
swift
@Test func invalidDiceRollsFail() async throws {
let dice = Dice()
// 🛡️ 关键:使用 #expect 包裹,并等待结果
await #expect(processExitsWith: .failure) {
// 在这里,roll(sides: 0) 会导致隔离的子进程崩溃退出
let _ = dice.roll(sides: 0)
}
// 如果子进程如期以 .failure 状态退出,则测试通过。
// 如果它没有崩溃,或者崩溃状态不对,则测试失败。
}
🔍 异步执行的关键:
await注意,这里必须使用await。这是因为在幕后,测试框架必须启动一个专用的、独立的进程 来执行危险代码。它会暂停当前测试,直到子进程运行完毕并返回退出状态。这才是真正的隔离测试!

阿紫满意地拍了拍手:"现在我的毒药(代码)终于可以在实验室(测试环境)里安全地爆炸了!我不仅可以测试它会死(failure),还可以测试它死得很安详(success)或其他退出状态。"
3️⃣ 🎁 尾声:崩溃现场的"遗物"与下一章的伏笔
侯佩摸了摸自己的头发,确认没有被阿紫的毒气熏掉,然后问道:"阿紫姑娘,你这个毒药测试虽然厉害,但是你有没有想过一个问题?"

"什么问题?"阿紫挑了挑眉。
"如果这个 roll(sides: 0) 崩溃了,但它在崩溃前,生成了一个关键的调试日志文件,或者一个记录了现场数据的**'遗物'**,你能不能把这个遗物附着到测试报告里?"
阿紫一愣:"不能。测试报告里只显示了'崩溃了'这个结果,但我不知道崩溃前骰子(程序)到底在想什么!我需要那个遗物来分析我的毒药配方!"

阿朱也附和道:"是啊,侯大哥。就像我易容时,如果失败了,我希望在失败的记录旁边,能附上一张当时的照片,这样下次就知道是哪个环节出了错。"
侯佩微微一笑,从怀里掏出了一张写着 ST-0009 的秘籍:"两位姑娘,不必烦恼。下一章,Swift Testing 就能帮你们把这些日志、数据和现场文件,像附着'随身物品'一样,直接捆绑到失败的测试报告上。这招就叫......"

(欲知后事如何,且看下回分解:Swift Testing: Attachments ------ 如何将崩溃现场的证据(日志、截图、数据文件)直接附着到测试报告上,让 Bug 无所遁形。)