33、重构与单元测试(一):为重构编写安全网 – 测试策略

重构这件事,说白了就是「不改功能,只改结构」。但问题来了——你怎么知道改完之后功能没变?

靠眼睛看?靠直觉?还是靠「我觉得没问题」?

嗯,我年轻时也这么干过。结果呢?上线当天晚上,线上炸了。从那以后,我养成了一个习惯:重构之前,先织一张安全网。这张网,就是测试。

为什么测试是重构的「安全网」?

你想想看,重构的本质是「在保持外部行为不变的前提下,改善内部结构」。但人的大脑是有极限的,改着改着就容易漏掉某个边界条件。这时候,测试就是你的第二双眼睛。

我个人习惯把测试分成三层:

  • 单元测试:验证单个函数/类的行为
  • 集成测试:验证模块之间的协作
  • 端到端测试:验证整个系统的关键路径

对于重构来说,单元测试是最核心的安全网。为什么?因为重构的粒度通常很小——改一个函数、拆一个类、提取一个接口。这些操作,单元测试都能精准覆盖。

核心原则:重构前,先确保被测代码有足够的测试覆盖率。我个人建议至少 80% 的行覆盖率,关键路径 100%。

测试策略:先写测试,再重构

很多人的习惯是「先改代码,再补测试」。但这样做有个坑——你改完之后,测试可能因为你的改动而「假绿」。说白了,测试本身可能已经失效了。

我建议的顺序是:

  1. 识别待重构的代码:找到那些「坏味道」——长函数、重复代码、过度耦合等
  2. 编写测试用例:针对当前行为写测试,覆盖正常路径、边界条件、异常情况
  3. 运行测试,确保全绿:这是你的基线
  4. 开始重构:每次改完一小步,就跑一次测试
  5. 测试持续全绿:说明重构没有破坏功能

小技巧:如果你发现某段代码「很难写测试」,那它往往就是最需要重构的地方。我在项目中遇到过很多次——代码耦合太紧,导致 mock 都 mock 不了。这时候,先拆耦合,再补测试,最后重构。

测试策略的四个维度

在实际项目中,我一般从四个维度来制定测试策略:

维度 说明 我的建议
覆盖度 代码行覆盖、分支覆盖、路径覆盖 行覆盖 ≥ 80%,分支覆盖 ≥ 70%
粒度 单元测试 vs 集成测试 vs E2E 单元测试占 70%,集成测试 20%,E2E 10%
执行速度 测试跑得越快,重构越频繁 单元测试 < 100ms/个,全量 < 5分钟
隔离性 测试之间不能互相影响 每个测试独立准备、执行、清理

你可能会问:「为什么单元测试要占这么多?」

原因很简单——重构的反馈循环越短,你越敢改。单元测试跑一次只要几毫秒,集成测试可能要几秒,E2E 可能要几分钟。你想想看,如果你每次改完都要等 5 分钟才知道有没有改坏,你还敢频繁重构吗?

一个真实的例子

我记得有一次重构一个订单处理模块。原来的代码是一个 300 行的函数,里面混杂着校验、计算、持久化、通知。我第一步不是改代码,而是先写测试。

我写了 12 个测试用例:

  • 正常订单:金额正确、状态流转正确
  • 边界订单:金额为 0、数量为 0
  • 异常订单:库存不足、用户余额不足
  • 并发场景:同一订单同时被两个请求处理

测试跑绿之后,我才开始拆函数。每拆出一个函数,就跑一次测试。整个过程大概花了 2 小时,测试一直全绿。最后上线,一次通过。

如果当时没有这张安全网,我估计至少要在线上修 3 个 bug。

避坑指南:我曾经犯过一个错误——测试写得太「脆弱」。比如直接断言某个私有方法的调用次数。结果重构时改了内部实现,测试就红了。但实际上功能没变。所以,测试应该关注「外部行为」,而不是「内部实现」

测试策略的落地流程

下面这张图是我在团队里推的测试策略流程,你可以参考:

识别坏味道 编写测试用例 运行测试全绿 开始重构 测试持续全绿 失败则回退 图:重构测试策略流程

常见测试策略模式

在实际工作中,我总结了几种常见的测试策略模式:

  • 黄金 master 模式:先跑一遍全量测试,确保当前代码是「绿的」。然后重构,再跑一遍。如果变红,说明改坏了。
  • 测试驱动重构(TDR):先写测试,再重构。测试就是你的「需求文档」。
  • 特性开关 + 测试:对于大型重构,可以用特性开关把新旧代码同时部署,通过测试对比输出。
  • 契约测试:当重构涉及接口变更时,用契约测试确保上下游的约定不被破坏。

我的习惯:每次重构前,我会先跑一次全量测试,把结果截图保存。重构过程中,每改完一个文件就跑一次测试。如果测试变红,我会立刻回退到上一个「绿点」,而不是继续往下改。这样能保证每次只改一个变量,问题定位非常快。

总结

测试不是重构的负担,而是重构的底气。没有安全网的重构,就像走钢丝不带保险绳——不是不能走,但摔一次就够你受的。

我个人建议:把「先写测试,再重构」变成肌肉记忆。刚开始可能会觉得慢,但等你习惯了,你会发现——重构的速度反而更快了。因为你不怕改坏了。

嗯,这一章就聊到这里。下一章我们聊聊具体的测试编写技巧——如何给遗留代码写测试。

公众号:蓝海资料掘金营,微信 deep3321