测试金字塔与 Spring Boot 定位

测试金字塔把测试分成三层:底层是大量快速、廉价的单元测试,中层是少量服务级测试,顶层是少数端到端测试。越往下越便宜、越快,越往上越接近真实环境但成本越高。

Spring Boot 的测试支持贯穿全部三层:JUnit 5 做单元测试,切片测试(Slice Test)和 MockMvc 覆盖控制器与数据层,Testcontainers 拉起真实容器跑集成测试。关键是每层测试各自聚焦,避免一测就启动整个应用。

很多项目的问题不是测试太少,而是集成测试又慢又不稳定,导致大家干脆不跑。合理分配测试层级,让大多数情况都能秒级反馈,测试才能真正跑起来。

测试的价值不在数量,而在能否在几秒内告诉你哪里坏了。
本文核心知识点
  • JUnit 5单元测试基础。
  • 切片测试@WebMvcTest 与 @DataJpaTest。
  • MockMvc控制器接口测试。
  • Testcontainers真实环境集成测试。
Spring Boot 测试配图:蓝调抽象插画中多层堆叠的蓝色立方体台座两侧对称环绕圆环轨道与试管状模块的科技场景
分层台座与试管环绕,像测试金字塔里逐层验证的服务结构。

JUnit 5 单元测试基础

单元测试只验证一个类的逻辑,不启动 Spring 容器。Spring Boot 项目里普通工具类、计算逻辑、校验规则的测试都属于这一层,用 JUnit 5 的注解就能写得很干净。

class OrderServiceTest {

    @Test
    void 满减计算应正确() {
        OrderService service = new OrderService();
        double result = service.discount(200, 0.1);
        assertThat(result).isEqualTo(180.0);
    }
}

断言库建议用 AssertJ,它的链式断言读起来接近自然语言,报错信息也更友好。单元测试要覆盖分支:正常路径、边界值、异常输入,这几类用例比数量更有意义。

纯单元测试毫秒级完成,能在 CI 里每次都跑。它们是最便宜的保险,值得把核心业务逻辑的覆盖率做上去。

@WebMvcTest 与 MockMvc

控制器测试不需要启动整个应用,用 @WebMvcTest 只加载 Web 层,配合 MockMvc 模拟 HTTP 请求,直接验证接口的路径、参数、状态码和响应体。

@WebMvcTest(UserController.class)
class UserControllerTest {

    @Autowired
    MockMvc mockMvc;

    @MockBean
    UserService userService;

    @Test
    void 查询用户应返回200() throws Exception {
        when(userService.find(1L)).thenReturn(new User(1L, "小码"));
        mockMvc.perform(get("/api/users/1"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.name").value("小码"));
    }
}

@MockBean 把依赖的服务替换成替身,让测试只关心控制器本身的逻辑:路由、参数绑定、状态码、响应结构。jsonPath 用来校验 JSON 响应里的具体字段。

MockMvc 不真正走网络,只在内核里模拟请求分发,速度快、可重复,是接口测试的主力。

数据层测试与 Testcontainers

数据层的 SQL 和映射逻辑用 @DataJpaTest 做切片测试,它只加载 JPA 相关配置并自动使用内存数据库。但内存数据库的行为和真实数据库有差异,敏感的 SQL 还是要在真库上验证。

Testcontainers 能在测试时用 Docker 拉起真实的 MySQL、PostgreSQL、Redis 容器,测试结束自动销毁。它让集成测试逼近生产环境,又不污染开发机。

@Testcontainers
class OrderRepositoryTest {

    @Container
    static PostgreSQLContainer<?> postgres =
        new PostgreSQLContainer<>("postgres:16");

    @Test
    void 自定义查询应返回正确结果() {
        // 连接 postgres.getJdbcUrl() 运行真实 SQL
    }
}

Testcontainers 首次拉镜像会慢一些,之后有缓存就会快很多。它是「数据库相关 bug 上线才暴露」这类问题的有力防线。

集成测试与事务回滚

集成测试验证多个组件协同:控制器、服务、仓库、数据库一条链路。用 @SpringBootTest 启动完整上下文,但为了可重复,测试数据要隔离,事务回滚是常用手段。

@SpringBootTest
@Transactional
class OrderFlowTest {

    @Autowired
    OrderService orderService;

    @Test
    void 下单后库存应扣减() {
        orderService.create(1L, 2);
        // 校验库存变化
    }
}

@Transactional 让每个测试方法结束后回滚,数据不会残留。注意 Testcontainers 场景下事务回滚与真实提交行为不同,涉及跨库或异步的用例要按真实提交验证。

集成测试数量要克制,挑关键业务链路写,否则整套测试会越来越慢,最后没人愿意等。