# 一次值得记下来的单测失败 > 一次值得记下来的单测失败。76 个用例挂了 2 个,挂的都是端到端用例:一个验证跨来源去重后只保留一份文件、并给对应记录打上标记;另一个验证最终路径上字节相同的内容会被并入既有记录 - Canonical page: https://obelisk.club/blog/build-log-1659 - Author: Peter Zhang - Published: 2026-10-11 - Tags: build-log 一次值得记下来的单测失败。76 个用例挂了 2 个,挂的都是端到端用例:一个验证跨来源去重后只保留一份文件、并给对应记录打上标记;另一个验证最终路径上字节相同的内容会被并入既有记录。纯文件逻辑,和商店、支付毫无关系。 而失败记录里抓到的错误摘录,是一条 StoreKit 错误:外层 SKServiceErrorDomain code 2,内层 SKInternalErrorDomain code 4,两层 localizedDescription 都是 (null)。 这个错位本身就是结论: 一、错误来自被测逻辑根本不碰的子系统时,它不是原因,是泄漏。商店服务错误出现在文件去重用例的记录里,机制几乎只剩一种:测试宿主(或被构造的对象)在启动路径上发了真实的商店调用,这类调用在单测环境里注定失败,失败对象又顺着某条全局错误路径漂进了不相干用例的记录。顺着它去调试去重逻辑,方向就全错了。 二、(null) 的描述不是没有信息,信息在 domain 和 code 里。Apple 一批服务级错误域造出来的错误,localizedDescription 天生为空,打印它等于打印空气。记录失败时带上 domain、code 和完整的底层错误链(NSUnderlyingError);只存一句描述的日志,真出事时什么也回答不了。 三、修法有顺序,顺序是承重的:先把失败记录修到可诊断(保留完整错误链),再把启动期的真实服务调用从测试宿主隔离出去——用测试宿主环境守卫直接跳过,或者把商店服务抽成协议、注入测试替身。顺序反了,你连替身有没有生效都验证不了。 一分钟自检:grep 一下启动路径里有没有无条件执行的商品拉取或权益刷新;或者干脆断网跑一遍单测——如果一堆与支付无关的用例集体变红、错误域以 SK 开头,你的测试宿主已经耦合了一个真实服务。 环境性失败在记录里长得和代码失败一模一样,只有错误域会出卖它。