主题
单元测试
一、为什么要写测试
- 回归保障:改一处代码,防止其他功能坏掉。
- 重构底气:有测试兜底,才敢动手优化代码。
- 简历加分:企业面试官很看重"会写测试"。
测试金字塔:
▲ 端到端测试(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=OrderServiceTestmvn test与mvn package都会执行测试。- 持续集成(第 9 部分)里,测试不过就不允许发布。
八、为核心业务写测试(重点)
给商城最核心的下单防超卖写测试预算:
java
@Test
void 并发下单不超卖() {
// 模拟乐观锁:条件更新成功返回 1
when(productMapper.reduceStock(1L, 2)).thenReturn(1);
// 连续调用断言库存逻辑正确
}九、本章验收
- [ ]
mvn test全绿通过 - [ ] 给
ProductService.list、OrderService.create各写至少 2 个用例(正常 + 异常) - [ ] 故意改坏一个逻辑,测试能拦下来(体验"回归保护")
好的测试准则
一个用例只测一件事,命名先中文一句话描述行为(库存不足时抛异常)。断言针对行为结果,而不是实现细节。