如何优雅地为“祖传代码”添加测试?
如何优雅地为“祖传代码”添加测试?
本文为阅读笔记,文章来源:The best way to start testing untested code
一、常见困境:为什么给遗留代码加测试这么难?
当你接手一个庞大、复杂且完全没有自动化测试的遗留代码库时,是不是总会陷入这种两难境地:
- • 代码如山,零测试:逻辑错综复杂,牵一发而动全身,任何改动都心惊胆战。
- • 时间紧迫,没空测:项目排期紧张,老板总觉得重构和补测试会拖慢进度。
- • 无从下手,怎么测:就算有时间,面对代码山,也不知道该从哪种测试开始。
很多人首先会想到两种主流的测试策略:端到端测试和单元测试。但在遗留代码面前,它们都有明显的局限性。
端到端测试 (E2E Test) 的痛点
虽然它能模拟真实用户场景,覆盖范围广,但是:
- • 设置太耗时:需要搭建完整的测试环境。
- • 运行太缓慢:跑一个用例可能要几分钟。
- • 反馈周期太长:不适合在开发过程中快速验证,等到CI挂了才发现问题,黄花菜都凉了。
单元测试 (Unit Test) 的窘境
虽然它运行快,反馈及时,但对于遗留代码:
- • 改造难度大:遗留代码通常不是为测试而设计的,依赖复杂,很难拆分和隔离。
- • 容易测歪:一不小心就变成了对实现细节的测试,导致代码一重构,测试就大片大片地挂掉。
- • 可能固化坏设计:为了让代码可测试而做的修改,有时反而会固化现有的不良结构。
二、最佳实践:从外到内,逐层击破
面对以上困境,文章提出一种更高效、更务实的策略,其核心思想就是:
从外部开始测试 (Start testing from the OUTSIDE), 从内部开始重构 (Refactor from the INSIDE)。
这个策略的精髓在于,我们不立即深入代码的汪洋大海,而是先为要修改的部分建立一个“安全网”,确保重构不破坏现有功能。有了这个安全网,我们就可以在网内充满信心地进行“外科手术式”的改造了。
三、解决方案:两步走策略
第一步:验收测试 (Approval Testing) - 快速构建安全网
验收测试是为遗留代码快速建立测试覆盖的超级利器。它不关心代码内部的逻辑对错,只关心在给定输入下,代码的输出行为是否与“已验收”的样本一致。
它也被称为 黄金副本测试 (Golden Master Testing) 或 快照测试 (Snapshot Testing)。
执行四步法:
- 1. 安排 (Arrange):准备测试所需的环境和输入数据。
- 2. 执行 (Act):运行需要被测试的代码逻辑。
- 3. 打印 (Printer):将代码执行产生的所有输出(返回值、状态变更、日志等)序列化为一个长字符串。
- 4. 断言 (Assert):
- • 首次运行:将生成的字符串保存为“黄金副本”文件。人工检查并“验收”。
- • 后续运行:将新生成的输出与“黄金副本”比对。如果不一致,测试就失败。
验收测试的巨大优势:
- • 快速建立安全网:只需运行一次并验证结果,就能为大块代码提供保护。
- • 无需完全理解代码:你不需要弄懂每一行,只需要确认整体行为符合预期。
- • 立即发现问题:任何对代码行为产生影响的修改都会立刻导致测试失败。
第二步:安全重构 (Safe Refactoring) - 在网内大胆改造
有了验收测试建立的安全网,我们就可以放心地开始重构了。
- • 处理小块代码:将大段复杂逻辑逐步拆解成更小、更易于管理的部分。
- • 提取有意义的部分:将可复用的逻辑提取成独立的函数或类。
- • 测试验证整体行为:在重构过程中,随时运行验收测试。只要测试通过,就证明你的修改是安全的。
- • 为新单元编写更好的测试:当你提取出设计良好的新单元时,就可以轻松地为它们编写高质量的单元测试了。
四、核心价值:告别调试地狱
采用“从外部测试,从内部重构”的策略,能为你带来巨大的价值:
- • 用最小成本建立信心:不再对修改遗留代码感到恐惧。
- • 安全重构,避免 Bug:大大降低在重构过程中引入新错误的风险。
- • 提升效率,告别调试地狱 :将大量时间从手动测试和事后调试中解放出来。
- • 熟练后比不测试更快:从长远看,它所节省的时间远超前期投入,最终会显著提升开发效率。