Skip to content

单元测试

一、为什么要写测试

  • 回归保障:改一处代码,防止其他功能坏掉。
  • 重构底气:有测试兜底,才敢动手优化代码。
  • 简历加分:企业面试官很看重"会写测试"。

测试金字塔:

        ▲  端到端测试(E2E,少)
      ▲  集成测试(中)
    ▲  单元测试(多,最快)

单元测试:单独测一个类/方法,不依赖数据库和网络。

二、依赖与项目结构

xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
src/test/java/com/mall/
└── service/
    └── OrderServiceTest.java

三、第一个单元测试:JUnit 5

测试 Service 层,用 Mockito 把 Mapper 替换成假的:

java
package com.mall.service;

import com.mall.mapper.ProductMapper;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

class ProductServiceTest {

    private final ProductMapper productMapper = mock(ProductMapper.class);

    @Test
    void 库存不足时抛异常() {
        // 准备:模拟"查询不到商品"或"扣减行数为 0"
        when(productMapper.selectById(1L)).thenReturn(null);

        assertThrows(BizException.class, () -> {
            // 调用业务方法(按实际代码调整)
            assertNotNull(productMapper.selectById(1L));
            throw new BizException("商品不存在");
        });
    }
}

四、用 Spring 容器做真实的单元测试

不需要启动 Web 服务,但需要 Spring 管理 Bean 时用 @SpringBootTest

java
package com.mall.service;

import com.mall.entity.Product;
import com.mall.mapper.ProductMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;

@ActiveProfiles("test")
@SpringBootTest
class ProductServiceIntegrationTest {

    @Autowired
    private ProductMapper productMapper;

    @Test
    void 数据库读写正常() {
        productMapper.deleteById(999L);   // 清理
        Product p = new Product();
        p.setName("测试商品");
        p.setPrice(new java.math.BigDecimal("9.9"));
        p.setStock(5);
        productMapper.insert(p);

        Product found = productMapper.selectById(p.getId());
        assertNotNull(found);
        assertEquals("测试商品", found.getName());
    }
}

五、测试分层建议

测什么依赖快慢
单元测试Service 逻辑、异常分支Mock Mapper毫秒级
Repository 测试Mapper/SQL真实内存库 H2 或测试库秒级
集成测试接口全链路真实 MySQL秒级
E2E 测试浏览器全流程Playwright/Cypress

六、常见断言

java
assertEquals(2, order.getItemCount());      // 相等
assertNotEquals(a, b);                      // 不等
assertTrue(order.getTotal() > 0);           // 为真
assertNotNull(order.getOrderNo());          // 非空
assertThrows(BizException.class, () -> service.create(dto));  // 断言抛异常

七、运行测试

bash
cd server
mvn test
# 只看某个测试类
mvn test -Dtest=OrderServiceTest
  • mvn testmvn package 都会执行测试。
  • 持续集成(第 9 部分)里,测试不过就不允许发布

八、为核心业务写测试(重点)

给商城最核心的下单防超卖写测试预算:

java
@Test
void 并发下单不超卖() {
    // 模拟乐观锁:条件更新成功返回 1
    when(productMapper.reduceStock(1L, 2)).thenReturn(1);
    // 连续调用断言库存逻辑正确
}

九、本章验收

  • [ ] mvn test 全绿通过
  • [ ] 给 ProductService.listOrderService.create 各写至少 2 个用例(正常 + 异常)
  • [ ] 故意改坏一个逻辑,测试能拦下来(体验"回归保护")

好的测试准则

一个用例只测一件事,命名先中文一句话描述行为(库存不足时抛异常)。断言针对行为结果,而不是实现细节。

基于 MIT 协议发布,可自由学习与修改