测试圈里有个说法:最好的测试是"改了一行代码,能立刻知道行为变了没有"。清算系统把这种理想变成了现实,因为它有一个绝大多数业务系统不具备的性质——确定性:同一交易日的同一批输入数据,跑出来的清算输出必然逐字节一致(不考虑时间戳这类环境噪声)。这个性质让黄金文件(golden file)回归成为清算测试的绝对主力。这篇讲这套测试体系怎么搭。
为什么是黄金文件而不是断言
传统单元测试的思路是"输入 → 断言输出等于期望值"。清算场景里这条路走不通,原因在输出维度太多:一批日终清算要生成几十种输出——资金流水、持仓流水、对账文件、报表,每种几百个字段。给每个字段写断言,工作量超过写被测代码本身,而且断言里手算的期望值本身就是错的来源——测试人员在 Excel 里手工验算一遍费用,和系统算的"不一致",最后发现是 Excel 公式错了。
黄金文件把问题掉转过来:不预测正确输出是什么,而是锁定已知的正确输出。流程是:对固定输入快照跑一次清算,把全部输出原样保存为基线;之后任何代码变更,重放同一输入,输出和基线逐字段 diff。diff 为空 = 行为无变化,变更安全;diff 非空 = 行为有变化,有 diff 不一定是坏事——如果这次变更本来就该改变行为(比如费率规则改了),diff 就是变更的预期效果本身。关键转变是:diff 从"测试失败"变成"变更说明文档",每个 diff 都要么被解释、要么被修复。
基线生成:生产快照脱敏回放
基线的质量决定整套体系的可信度,生成有三个讲究。
输入快照要真。用真实生产数据的一个交易日做输入,覆盖量才够——构造数据永远想不全真实世界的脏数据长什么样:零股持仓、负数余额、停牌股的在途委托。快照除了业务数据还要包括全部外部输入文件(交易所回报、结算公司清算数据、行情收盘文件),因为清算输出对这些文件的解析同样在被测范围。
脱敏要保住账务守恒。生产快照直接进测试环境有客户信息合规问题,必须脱敏。脱敏的坑在于账户号、客户号这类标识如果被随机替换,同一账户在快照各处的一致性就被破坏——流水表换了 ID、持仓表没换,账务直接对不上。正确做法是维护一张稳定映射表:同一原账户号在整个快照里始终映射到同一个假账户号。金额字段是否扰动是个权衡:扰动更安全但会破坏和外部文件里的金额勾稽,常见做法是金额不动、只换标识——前提是测试环境有和客户数据等价的访问管控。
基线版本化要和代码同仓。基线文件进 git(或用 LFS 存大文件),代码 PR 里能看到"本次变更改了哪些基线文件、diff 是什么"。这条纪律是整个体系的保险丝:如果有人改了代码顺路把基线也改了但 diff 没人 review,黄金文件体系就退化成"永远通过"的摆设。
对比器设计:容忍度的艺术
逐字节 diff 看似简单,实际输入输出里有大量合法噪声:流水号里有日期、报表头有运行时间、文件里有生成批次号。对比器需要一个容忍配置,把"每次运行都变的字段"声明出来排除掉。容忍配置的维护本身是代码评审对象——每次有人往里面加字段,reviewer 要问一句"这个字段为什么允许变化,它变化了会不会掩盖真实的 bug"。
金额对比有一个专门的坑:浮点。如果实现里存在浮点累加(《清算性能优化》里建议避免,但存量代码往往没避干净),输出金额可能有最后几位小数的抖动。对比器对金额字段应该用精度容忍(误差小于半个最小计价单位视为相等)而不是完全跳过——跳过等于放弃对金额的校验,容忍则保留了"误差超界即报警"的能力。
分层:黄金文件之外的补充
黄金文件管的是"全链路行为不变",但它定位 bug 的能力弱——diff 只能告诉你"资金流水某字段变了",不能告诉你哪行代码改的。所以体系里还要两层:
- 单元测试管算法正确性:费用计算、利息积数、除权价这些纯函数逻辑,输入输出维度小,断言写得动,用黄金文件反而臃肿。
- 任务级对比管定位:除了端到端基线,每个清算任务(费用、头寸、对账)单独存基线。端到端 diff 报警后,先跑任务级对比定位到具体任务,再看那个任务的代码 diff。没有这一层,大版本的定位要翻几百个文件的 diff。
回归跑的频率也值得说:清算基线回放一次要几十分钟到几小时,跑不进每次提交的 CI,常见做法是 nightly 全量 + 版本发布前强制。PR 级别的快速反馈靠单元测试和任务级子集,全量基线兜底。