1426 字
约 4 分钟
1
SpringBoot 测试
2026-09-19
无标签
第17章 SpringBoot 测试
掌握SpringBoot测试框架和最佳实践 💻 查看完整代码 - 在线IDE体验
🎯 学习目标
- 测试基础:了解测试理论和原则
- 单元测试:掌握JUnit和Mockito
- 集成测试:SpringBoot测试注解
- Web测试:MockMvc和TestRestTemplate
- 数据测试:@DataJpaTest等切片测试
- 测试策略:测试金字塔和最佳实践
🔧 核心概念
- Unit Test:单元测试
- Integration Test:集成测试
- Mock:模拟对象
- Stub:存根
- Test Slice:测试切片
- Test Context:测试上下文
📦 测试依赖配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 包含以下测试框架 -->
<!-- JUnit 5 - 测试框架 -->
<!-- Mockito - Mock框架 -->
<!-- AssertJ - 断言库 -->
<!-- Hamcrest - 匹配器 -->
<!-- Spring Test - Spring测试支持 -->
<!-- Spring Boot Test - SpringBoot测试支持 -->
<!-- 额外测试依赖 -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>mysql</artifactId>
<scope>test</scope>
</dependency>
🔄 测试执行流程
编写测试→配置环境→执行测试→验证结果→生成报告→持续集成
🏗️ 测试金字塔
测试策略分层
🌐 端到端测试 (E2E Tests) - 少量
🔗 集成测试 (Integration Tests) - 适量
🧪 单元测试 (Unit Tests) - 大量
🧪 单元测试示例
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Test
@DisplayName("根据ID查找用户 - 成功")
void findById_Success() {
// Given
Long userId = 1L;
User mockUser = new User(userId, "张三", "zhangsan@example.com");
when(userRepository.findById(userId)).thenReturn(Optional.of(mockUser));
// When
User result = userService.findById(userId);
// Then
assertThat(result).isNotNull();
assertThat(result.getId()).isEqualTo(userId);
assertThat(result.getName()).isEqualTo("张三");
verify(userRepository).findById(userId);
}
@Test
@DisplayName("根据ID查找用户 - 用户不存在")
void findById_UserNotFound() {
// Given
Long userId = 999L;
when(userRepository.findById(userId)).thenReturn(Optional.empty());
// When & Then
assertThatThrownBy(() -> userService.findById(userId))
.isInstanceOf(UserNotFoundException.class)
.hasMessage("用户不存在: " + userId);
}
@Test
@DisplayName("创建用户 - 成功")
void createUser_Success() {
// Given
CreateUserRequest request = new CreateUserRequest("李四", "lisi@example.com");
User savedUser = new User(2L, "李四", "lisi@example.com");
when(userRepository.save(any(User.class))).thenReturn(savedUser);
// When
User result = userService.createUser(request);
// Then
assertThat(result).isNotNull();
assertThat(result.getName()).isEqualTo("李四");
assertThat(result.getEmail()).isEqualTo("lisi@example.com");
}
}
🌐 Web层测试
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@Test
@DisplayName("获取用户信息 - 成功")
void getUser_Success() throws Exception {
// Given
Long userId = 1L;
User user = new User(userId, "张三", "zhangsan@example.com");
when(userService.findById(userId)).thenReturn(user);
// When & Then
mockMvc.perform(get("/api/users/{id}", userId))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(userId))
.andExpect(jsonPath("$.name").value("张三"))
.andExpect(jsonPath("$.email").value("zhangsan@example.com"));
}
@Test
@DisplayName("创建用户 - 成功")
void createUser_Success() throws Exception {
// Given
CreateUserRequest request = new CreateUserRequest("李四", "lisi@example.com");
User createdUser = new User(2L, "李四", "lisi@example.com");
when(userService.createUser(any(CreateUserRequest.class))).thenReturn(createdUser);
// When & Then
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.name").value("李四"))
.andExpect(jsonPath("$.email").value("lisi@example.com"));
}
}
🗄️ 数据层测试
@DataJpaTest
class UserRepositoryTest {
@Autowired
private TestEntityManager entityManager;
@Autowired
private UserRepository userRepository;
@Test
@DisplayName("根据邮箱查找用户")
void findByEmail_Success() {
// Given
User user = new User("张三", "zhangsan@example.com");
entityManager.persistAndFlush(user);
// When
Optional<User> found = userRepository.findByEmail("zhangsan@example.com");
// Then
assertThat(found).isPresent();
assertThat(found.get().getName()).isEqualTo("张三");
}
@Test
@DisplayName("根据名称查找用户列表")
void findByNameContaining_Success() {
// Given
entityManager.persistAndFlush(new User("张三", "zhangsan@example.com"));
entityManager.persistAndFlush(new User("张四", "zhangsi@example.com"));
entityManager.persistAndFlush(new User("李五", "liwu@example.com"));
// When
List<User> users = userRepository.findByNameContaining("张");
// Then
assertThat(users).hasSize(2);
assertThat(users).extracting(User::getName)
.containsExactlyInAnyOrder("张三", "张四");
}
}
🔗 集成测试示例
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestPropertySource(locations = "classpath:application-test.properties")
class UserIntegrationTest {
@Autowired
private TestRestTemplate restTemplate;
@Autowired
private UserRepository userRepository;
@LocalServerPort
private int port;
@BeforeEach
void setUp() {
userRepository.deleteAll();
}
@Test
@DisplayName("用户完整流程测试")
void userCompleteFlow() {
// 1. 创建用户
CreateUserRequest createRequest = new CreateUserRequest("张三", "zhangsan@example.com");
ResponseEntity<User> createResponse = restTemplate.postForEntity(
"/api/users", createRequest, User.class);
assertThat(createResponse.getStatusCode()).isEqualTo(HttpStatus.CREATED);
User createdUser = createResponse.getBody();
assertThat(createdUser).isNotNull();
assertThat(createdUser.getName()).isEqualTo("张三");
// 2. 查询用户
ResponseEntity<User> getResponse = restTemplate.getForEntity(
"/api/users/" + createdUser.getId(), User.class);
assertThat(getResponse.getStatusCode()).isEqualTo(HttpStatus.OK);
User retrievedUser = getResponse.getBody();
assertThat(retrievedUser).isNotNull();
assertThat(retrievedUser.getId()).isEqualTo(createdUser.getId());
// 3. 更新用户
UpdateUserRequest updateRequest = new UpdateUserRequest("张三丰", "zhangsan@example.com");
restTemplate.put("/api/users/" + createdUser.getId(), updateRequest);
// 4. 验证更新
ResponseEntity<User> updatedResponse = restTemplate.getForEntity(
"/api/users/" + createdUser.getId(), User.class);
User updatedUser = updatedResponse.getBody();
assertThat(updatedUser.getName()).isEqualTo("张三丰");
// 5. 删除用户
restTemplate.delete("/api/users/" + createdUser.getId());
// 6. 验证删除
ResponseEntity<String> deletedResponse = restTemplate.getForEntity(
"/api/users/" + createdUser.getId(), String.class);
assertThat(deletedResponse.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
}
}
🏷️ 测试注解对比
@SpringBootTest完整的Spring Boot应用上下文测试@WebMvcTestWeb层测试,只加载MVC相关组件@DataJpaTestJPA数据层测试,使用内存数据库@JsonTestJSON序列化和反序列化测试@TestConfiguration测试专用配置类@MockBeanSpring上下文中的Mock Bean
🧪 测试类型分类
单元测试 (Unit Tests)集成测试 (Integration Tests)端到端测试 (E2E Tests)性能测试 (Performance Tests)安全测试 (Security Tests)
🐳 Testcontainers集成
@SpringBootTest
@Testcontainers
class DatabaseIntegrationTest {
@Container
static MySQLContainer<?> mysql = new MySQLContainer<>("mysql:8.0")
.withDatabaseName("testdb")
.withUsername("test")
.withPassword("test");
@DynamicPropertySource
static void configureProperties(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 UserRepository userRepository;
@Test
@DisplayName("真实数据库环境测试")
void testWithRealDatabase() {
// Given
User user = new User("张三", "zhangsan@example.com");
// When
User saved = userRepository.save(user);
// Then
assertThat(saved.getId()).isNotNull();
assertThat(userRepository.findById(saved.getId())).isPresent();
}
}
📊 测试覆盖率配置
<!-- Maven Surefire Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M7</version>
<configuration>
<includes>
<include>**/*Test.java</include>
<include>**/*Tests.java</include>
</includes>
</configuration>
</plugin>
<!-- JaCoCo Coverage Plugin -->
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.8</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
📊 测试框架对比
| 测试框架 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| JUnit 5 | 功能强大,注解丰富 | 学习成本较高 | 单元测试 |
| Mockito | Mock功能完善 | 过度使用影响测试质量 | 依赖隔离 |
| TestContainers | 真实环境测试 | 启动速度较慢 | 集成测试 |
| WireMock | HTTP服务模拟 | 配置复杂 | 外部服务测试 |
| Selenium | 真实浏览器测试 | 维护成本高 | UI自动化测试 |
🎯 测试最佳实践
测试开发建议1. 测试命名:使用描述性的测试方法名2. AAA模式:Arrange-Act-Assert结构3. 独立性:测试之间不应相互依赖4. 快速反馈:单元测试应快速执行5. 覆盖率:追求有意义的测试覆盖6. 测试数据:使用测试专用数据7. 持续集成:自动化测试执行8. 测试维护:及时更新和重构测试
下一章节
🎉 恭喜完成第17章学习!
你已经掌握了SpringBoot测试的核心技能,接下来学习应用监控和管理! 🚀 下一章:SpringBoot Actuator 🏠 返回课程首页
评论
0 条
还没有评论,先写一条吧。