一句话定义:对拍验证(Differential Testing,也叫差分测试)是让两套本应行为一致的实现运行同一批输入,逐条比对它们的输出(返回值、抛出的错误、写下的数据、日志),把不一致的用例当场揪出来。
它像什么:老会计用算盘,新会计用电算机。你不会逐行去审查新会计的算法,而是让两人算同一批账,对不上的挑出来人工看。对拍就是这套思路:旧实现(比如 TypeScript 版本)充当“标准答案生成器”,新实现(Rust 版本)充当“考生”。
跨语言重写时它格外值钱,因为类型系统管不了语义。TypeScript 里整数除法、== 的隐式转换、数组越界返回 undefined、默认按字符串排序、JSON 大整数的精度、日期与时区、正则方言……这些都能编译、能跑,结果却悄悄变了。Rust 那边整数溢出在调试构建会 panic、发布构建会回绕,字符串按字节索引。编译器检查语法和类型,对拍检查行为。
用例怎么造:
- 真实输入打底:脱敏后的线上请求、历史日志、用户上传样本。
- 补边界:0、负数、空集合、
null/undefined、超长字符串、Unicode(emoji、组合字符)、跨时区、极大极小浮点。 - 用生成器批量造随机输入,再收缩(shrink)成最小复现用例。
- 关键一步:把“不确定”钉死。随机数种子、系统时钟、哈希遍历顺序、并发调度都要可控,否则对拍结果自己就抖(flaky),噪声淹没信号。
差异怎么收敛:先把差异分三类——真 bug、语言语义差异(预期内)、环境差异(浮点精度、排序稳定性);然后每条差异收成一个最小用例,进回归集,从红变绿;再定“金标准”:以旧实现为准还是以规范为准,写进文档;最后,归不了零的差异进显式白名单,每条注明原因,避免下次又被当成新 bug。
| 对拍验证 | 单元测试 | 类型检查 | |
|---|---|---|---|
| 判断依据 | 两套实现互相印证 | 人手写的预期值 | 语法与类型规则 |
| 擅长发现 | 语义漂移、边界差异 | 已想到的明确错误 | 结构性错误 |
| 发现不了 | 两边一起错 | 没想到的情况 | 一切运行时行为 |
对从业者的意义:让编码智能体(coding agent)做大迁移,对拍几乎是唯一客观的验收信号——智能体很擅长“看起来对”,对拍不看脸只看输出。对普通人的意义:任何换系统、换供应商、换版本的迁移,招式都一样——拿旧数据跑一遍,比对结果。别信演示,信对拍。
