3Working Effectively with Legacy Code

特征测试

建立安全网

特征测试回归测试测试覆盖黑盒测试

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第3章:特征测试(Characterization Tests)

一、导读

1.1 本章学习目标

第3章讲解特征测试——在理解代码之前先记录它的行为。

本章的核心学习目标包括:

  • 理解特征测试的目的和方法
  • 学会编写特征测试
  • 理解特征测试与回归测试的关系
  • 掌握处理不确定性的技术

1.2 为什么本章如此重要

面对遗留代码,你往往无法确定什么是"正确"的行为。特征测试不关心对错——它只记录系统实际做了什么。有了这个记录,你就可以安全地重构。

1.3 核心问题引导

特征测试和常规单元测试有什么区别?

如何处理测试中的不确定性(时间、随机数、外部依赖)?

特征测试应该覆盖什么?

特征测试如何帮助重构?


二、核心概念详解

2.1 特征测试的本质

java
// 特征测试:记录行为,不判断对错
@Test
void characterizeProcess() {
    LegacyProcessor processor = new LegacyProcessor();
    String result = processor.process("test input");
    // 不管结果看起来对不对,先记录下来
    assertEquals("actual_result_abc123", result);
}

// 有了这个测试,你可以安全地重构
// 如果重构后测试仍然通过,行为没有改变

2.2 处理不确定性

java
// 时间依赖
@Test
void characterizeWithTime() {
    // 方法1:使用固定时间
    // 方法2:记录相对行为
    // 方法3:使用接缝替换时间源
}

// 随机数
@Test
void characterizeWithRandom() {
    // 使用固定种子
    Random random = new Random(42);
    // 记录实际输出
}

// 外部依赖
@Test
void characterizeWithExternal() {
    // 使用 mock 或 stub
    // 记录实际交互
}

2.3 特征测试策略

1. 从关键路径开始:先测试最重要的功能
2. 边界值测试:测试极端输入
3. 错误路径测试:测试异常情况
4. 增量添加:逐步增加测试覆盖
5. 保持简单:不追求完美覆盖

三、实战案例

3.1 案例一:为复杂计算器编写特征测试

java
public class ComplexCalculator {
    public double calculate(String expression) {
        // 200 行复杂的解析和计算逻辑
        // 没有人完全理解它的行为
    }
}

// 特征测试
@Test
void characterizeCalculator() {
    ComplexCalculator calc = new ComplexCalculator();
    
    // 记录各种输入的实际输出
    assertEquals(3.0, calc.calculate("1 + 2"), 0.001);
    assertEquals(6.0, calc.calculate("2 * 3"), 0.001);
    assertEquals(-1.0, calc.calculate("1 - 2"), 0.001);
    
    // 边界情况
    assertEquals(0.0, calc.calculate("0 * 100"), 0.001);
    
    // 奇怪的行为?记录下来
    assertEquals(Double.NaN, calc.calculate("0 / 0"), 0.001);
    
    // 现在可以安全地重构了
}

四、本章小结

特征测试:记录系统实际行为,不判断对错。

处理不确定性:固定时间、固定种子、mock 外部依赖。

增量策略:从关键路径开始,逐步增加覆盖。

安全重构:有了特征测试,就可以安全地修改代码。


五、思考与练习

为一个你熟悉的遗留系统编写特征测试。

如何处理测试中的时间依赖?

特征测试应该追求多高的覆盖率?


3/7特征测试
0%