无人编写的回归测试:捕捉软件的真实行为
内容提要
长期运行的代码库往往存在两套规范:测试中写明的意图,以及实际运行中积累的未记录行为。用户常依赖这些意外行为(海勒姆定律),而普通回归测试无法覆盖。作者因此介绍“特征测试”:先记录代码当前的真实行为而非应有行为,以此作为安全网。借助黄金主测试、快照测试,或录制生产流量并在网络层回放,可低成本捕获服务行为及副作用。特征测试失败意味着行为改变,需判断是否有意为之。它尤其适用于遗留代码重构、框架升级、系统重写和AI辅助修改。
延伸解读
特征测试:先记录现实,再谈正确
特征测试的核心是“记录当前行为,而非应有行为”。它不判断对错,只把代码实际输出固定下来,作为安全网。这要求开发者暂时放下“这是bug”的直觉,接受“先锁定,再决定是否修改”的流程。对于遗留代码,这种测试能揭示大量未文档化的依赖,避免重构时意外破坏用户依赖的怪癖。
服务级特征测试:从流量中捕获副作用
服务的行为不仅体现在响应,还包括数据库查询、下游调用和消息发送。通过在生产环境网络层录制真实请求与响应,并连同依赖交互一起回放,可以低成本构建覆盖真实输入的特征测试套件。这种方法能捕捉到响应不变但内部行为改变的高成本回归,例如新增的查询在测试中无害却在生产环境造成压力。
失败含义不同:行为变化而非缺陷
特征测试失败只意味着行为发生了改变,而非一定是缺陷。团队需要判断变化是否故意为之:若是,则更新基线;若不是,则成功拦截了回归。在代码审查中查看这些差异,能促使团队将行为变化从意外转为决策。同时需注意过滤时间戳、生成ID等噪声字段,避免淹没真实信号。
适用场景:重构、升级、重写与AI辅助
特征测试在遗留代码重构、框架库升级、系统重写和AI辅助修改中价值显著。重构前锁定行为可减少恐惧;升级时能发现序列化顺序等细微变化;重写时旧服务的录制流量可作为新实现的规格;AI辅助修改时,预先捕获的基线是不共享修改假设的独立检查。这些场景共同点是行为复杂且意图测试覆盖不足。
Q&A
什么是特征测试(characterization test)?它和普通回归测试有什么区别?
特征测试是一种记录代码当前实际行为(而非应有行为)的测试方法。普通回归测试验证的是开发者写下的意图,而特征测试捕捉的是系统在长期运行中积累的未记录行为,包括意外行为。它的目标不是判断对错,而是建立安全网,一旦行为改变就发出警告。
为什么大多数团队不写特征测试?
主要原因是人力成本高:编写特征测试需要大量输入并仔细记录每个输出,对于整个服务则更繁琐。此外,开发者心理上抵触:断言自己认为是bug的行为感觉不对,需要转变观念才能理解固定bug正是为了未来变更时有人决策。
如何用生产流量来为服务编写特征测试?
在网络层录制服务的真实请求和响应,以及它对下游依赖(数据库、队列、其他服务)的调用。回放这些录制时,用同一份捕获数据应答依赖,从而固定服务的输入输出和副作用。如果响应改变或出现额外调用,差异就会显现。
特征测试失败时应该怎么处理?
特征测试失败只意味着行为改变了,不一定是bug。需要判断变更是否有意:如果是故意修复了怪癖并通知了依赖方,则新行为成为新基线;如果不是,则捕获了一个回归。团队应在PR中审查这些差异,将行为变更从意外变为决策。
特征测试最适合哪些场景?
最适合遗留代码重构(先固定行为再修改)、框架或库升级(捕捉细微行为变化)、系统重写(用旧服务录制流量作为新服务规格)以及AI辅助修改(提供独立于变更假设的基线检查)。
海勒姆定律是什么?它和特征测试有什么关系?
海勒姆定律指出,当足够多的人使用API时,系统的所有可观察行为都会被某些人依赖,包括未文档化的行为。特征测试正是为了捕捉这些被依赖的意外行为,防止无意中破坏依赖方。