一次全量单元测试,65 条里红了 1 条。被测的是跨资源去重的端到端流程(同内容只保留一份文件、给记录打上标记),和购买毫无关系;但失败记录里抓到的错误文本全是 StoreKit 测试会话的内部错误——SKTestSession 给某个内购条目设值失败,外层是 SKServiceErrorDomain Code=2,描述栏写着 "(null)",真正有信息量的代码压在底下的 NSUnderlyingError 里。
这个形状先教你两件事,再谈改代码:
一、失败摘录抓到的是“当时最吵的那一行”,不一定是“让断言失败的那一行”。测试宿主是一个进程,StoreKitTest 的会话是进程级状态:只要同宿主里任何购买路径的测试初始化过它,其余所有测试就都在和它共享同一个支付模拟器,它内部抛的错也会落进大家共用的日志。被测代码里一个字都没提 StoreKit,不影响这条耦合存在——这正是它平时看不见的机制:套件大体是绿的,偶发的一红还指向别处。
二、嵌套错误要从里往外读。外层域名配上 "(null)" 的描述等于零信息;拿外层域名去搜索只会把你带离真相,先拆 NSUnderlyingError 再下结论。
诊断顺序,反了就白费一轮:先读断言失败本身,再读日志尾巴——错误命名了被测流程根本不经过的子系统,就先按环境问题处理;然后单跑受害测试、全量跑、再做肇事配对二分——受害者在不同轮次间漂移,是共享状态或时序问题,不是逻辑错误;同一对稳定复现才是真正的状态污染。
修法是隔离,不是防御:把购买路径的测试挪进独立的测试宿主/目标,或者每个测试前后重置会话;给无关功能加防御性代码修不了进程级的耦合。
一分钟自检:在你的测试目标里 grep SKTestSession。如果它在一个同时跑非购买测试的宿主里被初始化,把整个套件连跑几遍,看有没有非购买测试带着 StoreKit 味道的日志红过——有,就是同一个坑。