Skip to content

测试体系

1. 这是什么

测试体系用于保障系统在持续修改中的正确性和稳定性。
它不是单一测试工具,而是一套覆盖不同层次的验证策略。

一句话理解:

  • 测试不是“为了跑绿”
  • 而是给系统演进建立安全网

2. 为什么重要

没有测试支撑,重构和迭代都会变得危险。
测试做得好,系统演进速度和质量通常都更稳。

真正有价值的测试体系可以回答这些问题:

  • 核心业务规则还对吗
  • 模块协作还对吗
  • 改动是否影响旧功能
  • 上线前有没有明显回归风险

3. 先建立直觉:不是所有测试都在做同一件事

最常见的误区之一是:

  • 觉得“只要有测试就行”

其实不同层次测试解决的问题不同:

测试层次更关注什么
单元测试单个类 / 单个规则是否正确
集成测试多个组件协作是否正确
接口测试对外行为是否符合契约
回归验证改动后旧功能有没有被破坏

所以测试体系不是一类测试,而是:

  • 多层测试各自承担不同职责

4. 核心内容

4.1 单元测试

单元测试更适合验证:

  • 纯业务规则
  • 计算逻辑
  • 状态转换

特点:

  • 速度快
  • 定位细
  • 更适合频繁运行

4.2 集成测试

集成测试更适合验证:

  • 组件之间是否正确协作
  • 配置是否正确
  • 数据访问链路是否正确

它比单元测试更接近真实运行环境,但成本也更高。

4.3 接口测试

接口测试更适合验证:

  • API 契约
  • 输入输出格式
  • 状态码和错误码

它更偏“对外行为是否符合预期”。

4.4 测试数据准备

很多测试之所以难维护,不是因为断言写得难,而是:

  • 测试数据准备混乱

一个好的测试数据设计应该尽量做到:

  • 可读
  • 最小化
  • 可复用
  • 不互相污染

4.5 回归验证

回归验证的价值在于:

  • 旧功能在新改动后仍然正确

这也是为什么测试体系和持续交付关系很大。
没有回归验证,系统越改越不敢发版。

5. 学习重点

这一章最重要的是掌握:

  • 不同测试层次的职责边界
  • 测试不是追求数量,而是追求有效覆盖
  • 单元测试更偏规则验证,集成测试更偏协作验证
  • 测试是重构和持续交付的安全网

6. 常见问题

6.1 只关注覆盖率,不关注测试价值

覆盖率是参考,不是目标本身。
无价值的测试即使很多,也挡不住真实 bug。

6.2 单元测试写得过于依赖实现细节

这样一重构就全碎,维护成本会很高。

6.3 缺少集成测试导致环境问题频发

只做单元测试,不代表组件协作就一定正确。

7. 动手验证

这一节我用纯 Java 做一个 mini test harness,帮助你直观看到“单元测试”和“集成测试”在关注点上的差异。

7.1 准备一个可运行示例

新建文件 TestingSystemDemo.java,内容如下:

java
public class TestingSystemDemo {
    interface DiscountRepository {
        int findDiscountByUser(long userId);
    }

    static class FakeDiscountRepository implements DiscountRepository {
        @Override
        public int findDiscountByUser(long userId) {
            return userId == 1L ? 20 : 0;
        }
    }

    static class PriceService {
        private final DiscountRepository repository;

        PriceService(DiscountRepository repository) {
            this.repository = repository;
        }

        int finalPrice(long userId, int amount) {
            return amount - repository.findDiscountByUser(userId);
        }
    }

    private static void assertEquals(String name, Object expected, Object actual) {
        if ((expected == null && actual != null) || (expected != null && !expected.equals(actual))) {
            throw new RuntimeException(name + " failed, expected=" + expected + ", actual=" + actual);
        }
        System.out.println(name + "=PASS");
    }

    public static void main(String[] args) {
        // 单元测试:只关注规则
        PriceService unitService = new PriceService(userId -> 10);
        assertEquals("unitTest-finalPrice", 90, unitService.finalPrice(999L, 100));

        // 集成测试:验证服务和仓储的协作
        PriceService integrationService = new PriceService(new FakeDiscountRepository());
        assertEquals("integrationTest-vipPrice", 80, integrationService.finalPrice(1L, 100));
        assertEquals("integrationTest-normalPrice", 100, integrationService.finalPrice(2L, 100));

        System.out.println("regressionChecklist=true");
    }
}

7.2 编译并运行

bash
javac TestingSystemDemo.java
java TestingSystemDemo

7.3 你应该观察到什么

输出应包含这些关键信息:

text
unitTest-finalPrice=PASS
integrationTest-vipPrice=PASS
integrationTest-normalPrice=PASS
regressionChecklist=true

7.4 每一行在验证什么

  • unitTest-finalPrice=PASS:说明单元测试更关注单个业务规则是否正确
  • integrationTest-vipPrice=PASSintegrationTest-normalPrice=PASS:说明集成测试更关注组件协作结果是否正确
  • regressionChecklist=true:说明回归验证本质上是在确认已有正确行为没有被破坏

8. 练习建议

下面这些练习做完,这一章会更扎实:

  • 为核心业务逻辑写一组单元测试
  • 为数据库或接口链路写一个集成测试
  • 设计一份回归验证清单
  • 复盘一个“覆盖率不低但还是出事故”的测试案例

9. 自测问题

  • 单元测试和集成测试的职责差别是什么?
  • 为什么测试是系统可持续演进的关键?
  • 什么样的测试最容易沦为无效测试?
  • 为什么测试数据设计会直接影响测试质量?

10. 自测核对要点

如果你的回答能覆盖下面这些点,说明这一章基本掌握到位了:

  • 不同测试层次解决的是不同问题
  • 单元测试更偏规则验证,集成测试更偏协作验证
  • 覆盖率不是唯一目标,有效覆盖更重要
  • 测试体系是重构、发版和回归验证的安全网