第 63 章:测试与排错
学习目标
- 写 JUnit 5 单元测试
- 用 Mockito 打桩
- 用 Testcontainers 做集成测试
- 用 Arthas 线上排错
一、JUnit 5 单元测试
java
@SpringBootTest
class UserServiceTest {
@Autowired
private UserService userService;
@Test
void testCreate() {
User user = new User();
user.setUsername("Tom");
user.setAge(18);
userService.create(user);
assertNotNull(user.getId());
assertEquals("Tom", user.getUsername());
}
@Test
void testGetById() {
User user = userService.create(new User("Tom", 18));
User found = userService.getById(user.getId());
assertEquals(18, found.getAge());
}
}⚠️ 坑 1:
@SpringBootTest启动慢(几秒到几十秒),单测不要用。要测 Service 直接 mock Mapper。
二、Mockito 单独测
java
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserMapper userMapper; // mock 依赖
@InjectMocks
private UserService userService; // 被测对象
@Test
void testGetById() {
User mockUser = new User(1L, "Tom", 18);
when(userMapper.selectById(1L)).thenReturn(Optional.of(mockUser));
User result = userService.getById(1L);
assertEquals("Tom", result.getUsername());
verify(userMapper, times(1)).selectById(1L); // 验证调用
}
@Test
void testGetByIdNotFound() {
when(userMapper.selectById(99L)).thenReturn(Optional.empty());
assertThrows(BusinessException.class,
() -> userService.getById(99L));
}
}⚠️ 坑 2:
@Mock/@InjectMocks用@ExtendWith(MockitoExtension.class),不要混 Spring。
三、断言
java
assertEquals(1, 1); // 等于
assertNotNull(user); // 非 null
assertTrue(list.isEmpty()); // 真
assertThrows(Exception.class, () -> foo()); // 抛异常
assertTimeout(Duration.ofSeconds(1), () -> foo()); // 不超时
// 批量
assertAll("user",
() -> assertEquals("Tom", user.getName()),
() -> assertEquals(18, user.getAge())
);四、Controller 测试(MockMvc)
java
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired
private MockMvc mvc;
@MockBean
private UserService userService;
@Test
void testGetById() throws Exception {
when(userService.getById(1L)).thenReturn(new User(1L, "Tom", 18));
mvc.perform(get("/api/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.data.username").value("Tom"));
}
}五、Testcontainers(集成测试)
真实数据库:
xml
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>mysql</artifactId>
<version>1.19.7</version>
<scope>test</scope>
</dependency>java
@SpringBootTest
@Testcontainers
class UserIntegrationTest {
@Container
static MySQLContainer<?> mysql = new MySQLContainer<>("mysql:8.0")
.withDatabaseName("test")
.withUsername("root")
.withPassword("test");
@DynamicPropertySource
static void datasource(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", mysql::getJdbcUrl);
registry.add("spring.datasource.username", mysql::getUsername);
registry.add("spring.datasource.password", mysql::getPassword);
}
@Autowired
private UserMapper userMapper;
@Test
void testInsert() {
User user = new User();
user.setUsername("Tom");
userMapper.insert(user);
assertNotNull(user.getId());
}
}优点:用真实 DB,测出 SQL 语法错、约束错。 缺点:每次启动慢(几秒到十几秒)。
六、覆盖率
xml
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.11</version>
</plugin>bash
./mvnw test
# 报告:target/site/jacoco/index.html目标:
- 单元测试覆盖率 > 80%
- 核心业务 > 95%
七、Arthas(线上排错)
bash
# 下载
curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar
# 选进程
# 然后:
dashboard # 概览
thread # 线程
watch demo.MathGame primeFactors "{params, returnObj, throwExp}" -x 2 # 监控方法
trace demo.MathGame primeFactors '#cost > 5' # 慢方法
jad com.example.UserService # 反编译⚠️ 生产慎用:
trace/watch影响性能,采样用。
八、本章小结
| 要点 | 关键 |
|---|---|
| 单元测试 | @ExtendWith(MockitoExtension.class) |
| Controller | @WebMvcTest + MockMvc |
| 集成 | Testcontainers |
| 覆盖率 | Jacoco > 80% |
| 排错 | Arthas |
动手练习
- 给
UserService写 5 个单元测试 - 用 Testcontainers 跑一次集成测试
下一章:第 64 章:Spring Boot 进阶与资源 →