1. 为什么需要为遗留代码添加单元测试
第一次接触那个十万行的老系统时,我差点被吓退。没有文档、没有测试、各种全局变量满天飞——这就是典型的"祖传代码"。但业务还得继续跑,新需求还得加,怎么办?单元测试就是破解这个困局的钥匙。
单元测试能带来三个直接好处:第一是安全网,修改代码时不用担心把其他地方搞坏;第二是活文档,测试用例本身就是对代码行为的说明;第三是设计改进器,写测试的过程会倒逼你解耦代码。去年我们给一个2005年的订单系统加了测试覆盖率后,缺陷率直接下降了60%。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 前期准备:评估与规划
2.1 代码库体检
先用代码可视化工具(如SonarQube)扫描整个项目,重点关注:
- 圈复杂度超过15的函数(红色警报)
- 超过100行的类(黄色警告)
- 静态方法调用超过5层的调用链
我习惯用Excel建个技术债清单,按严重程度排序。曾经有个支付模块的Calculate方法有28个参数,这种就是优先改造对象。
2.2 测试策略制定
根据代码特点选择测试策略:
- 新修改的代码:严格TDD
- 核心业务逻辑:从外层接口测试向内推进
- 工具类方法:直接单元测试
记住一个原则:先保证关键业务流有测试覆盖,再追求覆盖率数字。有次我花了三周把覆盖率刷到80%,结果发现漏测了最核心的折扣计算逻辑。
3. 实战技巧:测试改造五步法
3.1 解依赖技巧
遇到这样的代码怎么办?
java复制public void processOrder() {
DBConnection conn = GlobalDBPool.getConnection(); // 静态依赖
Printer printer = new Printer(); // 直接实例化
// 业务逻辑...
}
改造方案:
- 引入接口:
interface IDBConnection - 依赖注入:通过构造函数传入
- 使用Mock框架模拟依赖
注意:不要一次性改造所有依赖,先处理当前测试需要的部分
3.2 测试数据构造
老代码经常有这种魔法数字:
csharp复制if(userType == 3 && status == 7) {
// 神秘逻辑
}
建立测试数据工厂:
python复制def create_legacy_user():
return User(
type=3, # 黄金会员
status=7 # 待激活
)
加上清晰的注释说明这些魔法值的业务含义。
3.3 测试用例设计
采用"倒推法"设计用例:
- 先写一个最简测试运行目标代码
- 观察代码覆盖率报告
- 针对未覆盖的分支补充用例
我有个检查清单:
- 边界值测试(特别是数值计算)
- 异常路径测试(文件不存在、网络超时等)
- 多线程并发场景
4. 持续改进策略
4.1 测试执行优化
在CI流水线中设置测试分级:
- L1:核心业务测试(每次提交必跑)
- L2:非关键路径测试(每日夜间执行)
- L3:性能测试(每周执行)
用Tag标记测试类别:
java复制@Test
@Category(CriticalTest.class)
public void testPaymentProcess() {...}
4.2 覆盖率提升计划
建议采用"20-60-20"原则:
- 先用一个月让关键模块达到20%覆盖率
- 再用三个月提升到60%
- 最后逐步优化到80%以上
每周同步覆盖率增长曲线,让团队看到进展。我们在看板上用温度计可视化这个进度,效果很好。
5. 常见问题解决方案
5.1 无法实例化的类
遇到这种代码:
csharp复制private class InnerClass {
// 没有公共构造函数
}
解决方案:
- 使用反射强制实例化
- 将类改为protected并添加@VisibleForTesting注解
- 提取接口进行测试
5.2 静态方法依赖
对于全局的Logger、Config等:
java复制public class OrderService {
public void createOrder() {
AppLogger.log("start create"); // 静态调用
}
}
改造方案:
- 引入Wrapper类
- 使用PowerMock模拟静态方法
- 逐步重构为实例方法
6. 工具链推荐
我的测试工具组合:
- Java: JUnit5 + Mockito + AssertJ
- C#: xUnit + NSubstitute + FluentAssertions
- 前端: Jest + Testing Library
- 覆盖率: JaCoCo / Coverlet
特别推荐ArchUnit用于架构约束测试,可以防止新增代码破坏既有架构规则。
7. 团队协作建议
推行"测试所有权"制度:
- 谁修改代码,谁负责补充测试
- CR时必须有测试用例
- 设立"测试守护者"角色
我们团队有个传统:每次发现生产环境bug,先写回归测试再修代码。两年下来,类似的bug再没出现过第二次。
