Spring Boot 后端开发测试全指南
目录
- 测试层级体系
- 单元测试
- 集成测试
- [API 测试工具](#API 测试工具)
- 自动化测试框架
- 性能测试
- 企业测试最佳实践
- [CI/CD 集成测试](#CI/CD 集成测试)
- [@AutoConfigureMockMvc 详解](#@AutoConfigureMockMvc 详解)
- 测试策略对比
一、测试层级体系
企业中后端测试通常分为以下几个层级:
┌─────────────────────────────────────────────────────────────┐
│ 测试金字塔 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ▲ E2E Tests (少量) │
│ / \ │
│ / \ │
│ / \ │
│ / \ │
│ / 集成测试 \ (适量) │
│ / \ │
│ / \ │
│ / \ │
│ / 单元测试 \ (大量) │
│ /___________________\ │
│ │
└─────────────────────────────────────────────────────────────┘
测试层级说明
| 测试类型 | 覆盖率要求 | 执行速度 | 维护成本 | 适用场景 |
|---|---|---|---|---|
| 单元测试 | 80%+ | 快 | 低 | 业务逻辑测试 |
| 集成测试 | 60%+ | 中 | 中 | API接口测试 |
| E2E测试 | 20%+ | 慢 | 高 | 关键业务流程 |
二、单元测试
1. Repository 层测试
使用 @DataJpaTest 进行 Repository 层测试:
java
@DataJpaTest
class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
@Test
void testFindByUsername() {
userRepository.findByUsername("user").ifPresent(System.out::println);
}
@Test
void testFindByEmail() {
User user = new User("testuser", "test@example.com", "password");
userRepository.save(user);
Optional<User> found = userRepository.findByEmail("test@example.com");
assertTrue(found.isPresent());
assertEquals("testuser", found.get().getUsername());
}
@Test
void testExistsByUsername() {
User user = new User("testuser", "test@example.com", "password");
userRepository.save(user);
assertTrue(userRepository.existsByUsername("testuser"));
assertFalse(userRepository.existsByUsername("nonexistent"));
}
}
2. Service 层测试
使用 Mockito 进行 Service 层单元测试:
java
@ExtendWith(MockitoExtension.class)
class AuthServiceTest {
@Mock
private UserRepository userRepository;
@Mock
private AuthenticationManager authenticationManager;
@Mock
private JwtUtil jwtUtil;
@Mock
private RefreshTokenService refreshTokenService;
@Mock
private UserDetailsService userDetailsService;
@InjectMocks
private AuthService authService;
@Test
void testAuthenticateUser_Success() {
// Given
LoginRequest request = LoginRequest.builder()
.username("testuser")
.password("password")
.build();
User user = new User("testuser", "test@example.com", "encodedPassword");
Authentication authentication = mock(Authentication.class);
when(authenticationManager.authenticate(any())).thenReturn(authentication);
when(userRepository.findByUsername("testuser")).thenReturn(Optional.of(user));
when(jwtUtil.generateToken(any())).thenReturn("jwt-token");
when(refreshTokenService.createRefreshToken(any())).thenReturn(new RefreshToken());
// When
JwtResponse response = authService.authenticateUser(request);
// Then
assertNotNull(response);
assertEquals("testuser", response.getUsername());
verify(authenticationManager).authenticate(any());
verify(jwtUtil).generateToken(any());
}
@Test
void testRegisterUser_Success() {
// Given
RegisterRequest request = RegisterRequest.builder()
.username("newuser")
.email("new@example.com")
.password("password123")
.build();
when(userRepository.existsByUsername("newuser")).thenReturn(false);
when(userRepository.existsByEmail("new@example.com")).thenReturn(false);
// When
authService.registerUser(request);
// Then
verify(userRepository).save(any(User.class));
}
@Test
void testRegisterUser_ExistingUsername_ThrowsException() {
// Given
RegisterRequest request = RegisterRequest.builder()
.username("existinguser")
.email("new@example.com")
.password("password123")
.build();
when(userRepository.existsByUsername("existinguser")).thenReturn(true);
// When & Then
assertThrows(RuntimeException.class, () -> authService.registerUser(request));
}
}
3. Controller 层测试
使用 @WebMvcTest 进行 Controller 层单元测试:
java
@WebMvcTest(AuthController.class)
class AuthControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private AuthService authService;
@Autowired
private ObjectMapper objectMapper;
@Test
void testLogin_Success() throws Exception {
// Given
LoginRequest request = LoginRequest.builder()
.username("testuser")
.password("password")
.build();
JwtResponse response = JwtResponse.builder()
.accessToken("jwt-token")
.refreshToken("refresh-token")
.username("testuser")
.email("test@example.com")
.roles(List.of("ROLE_USER"))
.build();
when(authService.authenticateUser(any())).thenReturn(response);
// When & Then
mockMvc.perform(post("/api/auth/login")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.accessToken").value("jwt-token"))
.andExpect(jsonPath("$.username").value("testuser"))
.andExpect(jsonPath("$.email").value("test@example.com"));
}
@Test
void testRegister_Success() throws Exception {
// Given
RegisterRequest request = RegisterRequest.builder()
.username("newuser")
.email("new@example.com")
.password("password123")
.build();
doNothing().when(authService).registerUser(any());
// When & Then
mockMvc.perform(post("/api/auth/register")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isOk())
.andExpect(content().string("User registered successfully!"));
}
@Test
void testLogin_InvalidCredentials_BadRequest() throws Exception {
// Given
LoginRequest request = LoginRequest.builder()
.username("testuser")
.password("wrongpassword")
.build();
when(authService.authenticateUser(any()))
.thenThrow(new RuntimeException("Invalid credentials"));
// When & Then
mockMvc.perform(post("/api/auth/login")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isBadRequest());
}
}
三、集成测试
1. 完整集成测试示例
java
@SpringBootTest
@AutoConfigureMockMvc
@Transactional
@ActiveProfiles("test")
class AuthIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private UserRepository userRepository;
@Autowired
private RoleRepository roleRepository;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private ObjectMapper objectMapper;
@BeforeEach
void setUp() {
// 准备测试数据
Role userRole = new Role(Role.RoleName.ROLE_USER);
roleRepository.save(userRole);
}
@Test
void testCompleteAuthenticationFlow() throws Exception {
// 1. 注册用户
RegisterRequest registerRequest = RegisterRequest.builder()
.username("testuser")
.email("test@example.com")
.password("password123")
.build();
mockMvc.perform(post("/api/auth/register")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(registerRequest)))
.andExpect(status().isOk())
.andExpect(content().string("User registered successfully!"));
// 2. 登录获取token
LoginRequest loginRequest = LoginRequest.builder()
.username("testuser")
.password("password123")
.build();
String loginResponse = mockMvc.perform(post("/api/auth/login")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(loginRequest)))
.andExpect(status().isOk())
.andReturn()
.getResponse()
.getContentAsString();
JwtResponse jwtResponse = objectMapper.readValue(loginResponse, JwtResponse.class);
String token = jwtResponse.getAccessToken();
// 3. 使用token访问受保护接口
mockMvc.perform(get("/api/user/profile")
.header("Authorization", "Bearer " + token))
.andExpect(status().isOk())
.andExpect(jsonPath("$.username").value("testuser"))
.andExpect(jsonPath("$.email").value("test@example.com"));
}
@Test
void testLoginWithInvalidCredentials() throws Exception {
// 1. 注册用户
RegisterRequest registerRequest = RegisterRequest.builder()
.username("testuser")
.email("test@example.com")
.password("password123")
.build();
mockMvc.perform(post("/api/auth/register")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(registerRequest)))
.andExpect(status().isOk());
// 2. 使用错误密码登录
LoginRequest loginRequest = LoginRequest.builder()
.username("testuser")
.password("wrongpassword")
.build();
mockMvc.perform(post("/api/auth/login")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(loginRequest)))
.andExpect(status().isUnauthorized());
}
}
四、API 测试工具
1. Postman/Apifox 手动测试
优点:
- ✅ 直观易用,无需编码
- ✅ 支持环境变量和参数化
- ✅ 可以保存测试集合
- ✅ 支持自动化测试脚本
Postman 测试脚本示例:
javascript
// 测试脚本
pm.test("Status code is 200", function () {
pm.response.to.have.status(200);
});
pm.test("Response has access token", function () {
var jsonData = pm.response.json();
pm.expect(jsonData.accessToken).to.exist;
});
pm.test("Response has username", function () {
var jsonData = pm.response.json();
pm.expect(jsonData.username).to.equal("testuser");
});
// 保存token到环境变量
var jsonData = pm.response.json();
pm.environment.set("access_token", jsonData.accessToken);
pm.environment.set("refresh_token", jsonData.refreshToken);
Apifox 测试用例示例:
json
{
"name": "用户登录测试",
"request": {
"method": "POST",
"url": "{{baseUrl}}/api/auth/login",
"header": {
"Content-Type": "application/json"
},
"body": {
"username": "testuser",
"password": "password123"
}
},
"tests": {
"状态码200": "pm.response.to.have.status(200)",
"包含token": "pm.response.json().accessToken !== undefined",
"包含用户名": "pm.response.json().username === 'testuser'"
}
}
2. cURL 命令行测试
bash
# 注册用户
curl -X POST http://localhost:8080/api/auth/register \
-H "Content-Type: application/json" \
-d '{
"username": "testuser",
"email": "test@example.com",
"password": "password123"
}'
# 登录获取token
curl -X POST http://localhost:8080/api/auth/login \
-H "Content-Type: application/json" \
-d '{
"username": "testuser",
"password": "password123"
}' \
-o login_response.json
# 使用token访问受保护接口
TOKEN=$(cat login_response.json | jq -r '.accessToken')
curl -X GET http://localhost:8080/api/user/profile \
-H "Authorization: Bearer $TOKEN"
# 刷新token
curl -X POST "http://localhost:8080/api/auth/refresh-token?refreshToken=$REFRESH_TOKEN"
五、自动化测试框架
1. TestRestTemplate 集成测试
java
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class AuthControllerIntegrationTest {
@Autowired
private TestRestTemplate restTemplate;
@LocalServerPort
private int port;
@Autowired
private UserRepository userRepository;
@Autowired
private PasswordEncoder passwordEncoder;
@Test
void testLoginWithRestTemplate() {
// 准备测试数据
User user = new User("testuser", "test@example.com",
passwordEncoder.encode("password123"));
userRepository.save(user);
// 执行登录
String url = "http://localhost:" + port + "/api/auth/login";
LoginRequest request = LoginRequest.builder()
.username("testuser")
.password("password123")
.build();
ResponseEntity<JwtResponse> response = restTemplate.postForEntity(
url, request, JwtResponse.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertNotNull(response.getBody().getAccessToken());
assertEquals("testuser", response.getBody().getUsername());
}
}
2. RestAssured API 测试
java
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class AuthControllerRestAssuredTest {
@LocalServerPort
private int port;
@Autowired
private UserRepository userRepository;
@Autowired
private PasswordEncoder passwordEncoder;
@BeforeEach
void setUp() {
User user = new User("testuser", "test@example.com",
passwordEncoder.encode("password123"));
userRepository.save(user);
}
@Test
void testLoginWithRestAssured() {
given()
.port(port)
.contentType("application/json")
.body(LoginRequest.builder()
.username("testuser")
.password("password123")
.build())
.when()
.post("/api/auth/login")
.then()
.statusCode(200)
.body("accessToken", notNullValue())
.body("username", equalTo("testuser"))
.body("email", equalTo("test@example.com"));
}
@Test
void testLoginWithInvalidCredentials() {
given()
.port(port)
.contentType("application/json")
.body(LoginRequest.builder()
.username("testuser")
.password("wrongpassword")
.build())
.when()
.post("/api/auth/login")
.then()
.statusCode(401);
}
}
六、性能测试
1. JMeter 压力测试
xml
<!-- JMeter 测试计划 -->
<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2">
<hashTree>
<TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="API Performance Test">
<elementProp name="TestPlan.user_defined_variables" elementType="Arguments">
<collectionProp name="Arguments.arguments">
<elementProp name="BASE_URL" elementType="Argument">
<stringProp name="Argument.name">BASE_URL</stringProp>
<stringProp name="Argument.value">http://localhost:8080</stringProp>
</elementProp>
</collectionProp>
</elementProp>
</TestPlan>
<hashTree>
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="User Group">
<stringProp name="ThreadGroup.num_threads">100</stringProp>
<stringProp name="ThreadGroup.ramp_time">10</stringProp>
<stringProp name="ThreadGroup.duration">60</stringProp>
</ThreadGroup>
<hashTree>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Login Request">
<stringProp name="HTTPSampler.domain">${BASE_URL}</stringProp>
<stringProp name="HTTPSampler.port">8080</stringProp>
<stringProp name="HTTPSampler.path">/api/auth/login</stringProp>
<stringProp name="HTTPSampler.method">POST</stringProp>
</HTTPSamplerProxy>
</hashTree>
</hashTree>
</hashTree>
</jmeterTestPlan>
2. Gatling 性能测试
scala
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import scala.concurrent.duration._
class LoginSimulation extends Simulation {
val httpProtocol = http
.baseUrl("http://localhost:8080")
.acceptHeader("application/json")
.contentTypeHeader("application/json")
val scn = scenario("Login Scenario")
.exec(http("Login Request")
.post("/api/auth/login")
.body(StringBody("""{
"username": "testuser",
"password": "password123"
}"""))
.check(status.is(200))
.check(jsonPath("$.accessToken").saveAs("token")))
.exec(http("Get User Profile")
.get("/api/user/profile")
.header("Authorization", "Bearer ${token}")
.check(status.is(200)))
setUp(
scn.inject(
rampUsers(10).during(10.seconds),
constantUsersPerSec(20).during(30.seconds),
rampUsers(100).during(20.seconds)
)
).protocols(httpProtocol)
}
七、企业测试最佳实践
1. 测试覆盖率要求
| 测试类型 | 覆盖率要求 | 说明 |
|---|---|---|
| 单元测试 | 80%+ | 核心业务逻辑 |
| 集成测试 | 60%+ | API接口测试 |
| E2E测试 | 20%+ | 关键业务流程 |
2. 测试命名规范
java
// 测试方法命名:test{方法名}_{场景}_{预期结果}
@Test
void testAuthenticateUser_ValidCredentials_ReturnsJwtResponse() {
}
@Test
void testAuthenticateUser_InvalidCredentials_ThrowsException() {
}
@Test
void testRegisterUser_ExistingUsername_ThrowsException() {
}
@Test
void testRegisterUser_ExistingEmail_ThrowsException() {
}
@Test
void testRefreshToken_ValidToken_ReturnsNewAccessToken() {
}
3. 测试数据管理
java
@SpringBootTest
class TestDataManagementTest {
@Autowired
private UserRepository userRepository;
@Test
void testWithTestData() {
// 使用 @Sql 注解准备测试数据
// @Sql("/data/test-users.sql")
// 或者使用测试数据构建器
User user = UserTestDataBuilder.aUser()
.withUsername("testuser")
.withEmail("test@example.com")
.withPassword("password123")
.withRole("ROLE_USER")
.build();
userRepository.save(user);
// 执行测试
User found = userRepository.findByUsername("testuser").orElseThrow();
assertEquals("test@example.com", found.getEmail());
}
}
// 测试数据构建器
class UserTestDataBuilder {
private String username = "defaultuser";
private String email = "default@example.com";
private String password = "defaultpassword";
private Set<Role> roles = new HashSet<>();
public static UserTestDataBuilder aUser() {
return new UserTestDataBuilder();
}
public UserTestDataBuilder withUsername(String username) {
this.username = username;
return this;
}
public UserTestDataBuilder withEmail(String email) {
this.email = email;
return this;
}
public UserTestDataBuilder withPassword(String password) {
this.password = password;
return this;
}
public UserTestDataBuilder withRole(String roleName) {
Role role = new Role(Role.RoleName.valueOf(roleName));
roles.add(role);
return this;
}
public User build() {
return new User(username, email, password);
}
}
4. 测试隔离策略
java
@SpringBootTest
@Transactional // 每个测试后自动回滚
class TestIsolationExample {
@Autowired
private UserRepository userRepository;
@BeforeEach
void setUp() {
// 每个测试前的准备
}
@AfterEach
void tearDown() {
// 每个测试后的清理(@Transactional 会自动回滚)
}
@Test
void test1() {
// 这个测试的数据不会影响 test2
User user = new User("user1", "user1@example.com", "password");
userRepository.save(user);
}
@Test
void test2() {
// 这个测试开始时数据库是干净的
long count = userRepository.count();
assertEquals(0, count);
}
}
八、CI/CD 集成测试
1. GitHub Actions 配置
yaml
name: Backend Tests
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
cache: 'maven'
- name: Run unit tests
run: mvn test
- name: Run integration tests
run: mvn verify
- name: Generate test report
run: mvn jacoco:report
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
files: ./target/site/jacoco/jacoco.xml
flags: unittests
name: codecov-umbrella
- name: Upload test results
if: always()
uses: actions/upload-artifact@v3
with:
name: test-results
path: target/surefire-reports/*.xml
2. Jenkins Pipeline
groovy
pipeline {
agent any
tools {
maven 'Maven 3.8'
jdk 'JDK 17'
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Unit Tests') {
steps {
sh 'mvn clean test'
}
post {
always {
junit 'target/surefire-reports/*.xml'
}
}
}
stage('Integration Tests') {
steps {
sh 'mvn verify'
}
post {
always {
junit 'target/failsafe-reports/*.xml'
}
}
}
stage('API Tests') {
steps {
sh 'newman run postman_collection.json'
}
}
stage('Code Coverage') {
steps {
sh 'mvn jacoco:report'
publishHTML([
reportDir: 'target/site/jacoco',
reportFiles: 'index.html',
reportName: 'Coverage Report'
])
}
}
}
post {
always {
cleanWs()
}
}
}
九、@AutoConfigureMockMvc 详解
1. 什么是 @AutoConfigureMockMvc?
@AutoConfigureMockMvc 是 Spring Boot 提供的测试注解,用于在集成测试中自动配置 MockMvc 实例。
java
@SpringBootTest
@AutoConfigureMockMvc
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
void testGetUserProfile() throws Exception {
mockMvc.perform(get("/api/user/profile"))
.andExpect(status().isOk());
}
}
2. 核心作用
| 功能 | 说明 |
|---|---|
| 自动配置 | 自动配置 MockMvc Bean |
| 完整上下文 | 加载完整的 Spring 应用上下文 |
| 真实环境 | 模拟真实的 HTTP 请求/响应 |
| 集成测试 | 适合 Controller 层集成测试 |
3. MockMvc 工作原理
┌─────────────────────────────────────────────────────────────┐
│ MockMvc 工作流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 测试代码 → MockMvc → DispatcherServlet → Controller → 响应 │
│ ↓ ↓ ↓ ↓ │
│ 发送请求 模拟HTTP Spring MVC 业务逻辑 │
│ 验证响应 请求/响应 处理流程 返回数据 │
│ │
└─────────────────────────────────────────────────────────────┘
4. MockMvc 核心方法
请求构建方法:
java
// HTTP 方法
mockMvc.perform(get("/api/users")) // GET 请求
mockMvc.perform(post("/api/users")) // POST 请求
mockMvc.perform(put("/api/users/1")) // PUT 请求
mockMvc.perform(delete("/api/users/1")) // DELETE 请求
mockMvc.perform(patch("/api/users/1")) // PATCH 请求
// 请求参数
mockMvc.perform(get("/api/users")
.param("page", "0")
.param("size", "10"))
// 请求头
mockMvc.perform(get("/api/users")
.header("Authorization", "Bearer token")
.header("Content-Type", "application/json"))
// 请求体
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{
"username": "testuser",
"email": "test@example.com"
}
"""))
响应验证方法:
java
// 状态码验证
.andExpect(status().isOk()) // 200
.andExpect(status().isCreated()) // 201
.andExpect(status().isBadRequest()) // 400
.andExpect(status().isUnauthorized()) // 401
.andExpect(status().isForbidden()) // 403
.andExpect(status().isNotFound()) // 404
// 响应内容验证
.andExpect(content().string("expected content"))
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.username").value("testuser"))
.andExpect(jsonPath("$.users").isArray())
.andExpect(jsonPath("$.users[0].email").exists())
// 响应头验证
.andExpect(header().string("Content-Type", "application/json"))
.andExpect(header().exists("Authorization"))
// 重定向验证
.andExpect(redirectedUrl("/login"))
.andExpect(forwardedUrl("/error"))
结果处理方法:
java
// 打印详细信息
.andDo(print()) // 打印请求和响应
.andDo(log()) // 记录日志
// 获取响应结果
MvcResult result = mockMvc.perform(get("/api/users"))
.andReturn();
String response = result.getResponse().getContentAsString();
int status = result.getResponse().getStatus();
5. 完整示例
java
@SpringBootTest
@AutoConfigureMockMvc
@Transactional
@ActiveProfiles("test")
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private UserRepository userRepository;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private JwtUtil jwtUtil;
private String userToken;
private String adminToken;
@BeforeEach
void setUp() {
// 准备测试用户
User user = new User("testuser", "user@example.com",
passwordEncoder.encode("password123"));
Role userRole = new Role(Role.RoleName.ROLE_USER);
user.setRoles(Set.of(userRole));
userRepository.save(user);
// 生成测试 token
UserDetails userDetails = org.springframework.security.core.userdetails.User
.withUsername("testuser")
.password("password123")
.roles("USER")
.build();
userToken = jwtUtil.generateToken(userDetails);
}
@Test
void testGetCurrentUserProfile_Success() throws Exception {
mockMvc.perform(get("/api/user/profile")
.header("Authorization", "Bearer " + userToken))
.andExpect(status().isOk())
.andExpect(jsonPath("$.username").value("testuser"))
.andExpect(jsonPath("$.email").value("user@example.com"));
}
@Test
void testGetCurrentUserProfile_WithoutToken_Unauthorized() throws Exception {
mockMvc.perform(get("/api/user/profile"))
.andExpect(status().isUnauthorized());
}
@Test
void testGetUserDashboard_Success() throws Exception {
mockMvc.perform(get("/api/user/dashboard")
.header("Authorization", "Bearer " + userToken))
.andExpect(status().isOk())
.andExpect(content().string(containsString("User Dashboard")));
}
@Test
void testGetUserDashboard_InvalidToken_Forbidden() throws Exception {
mockMvc.perform(get("/api/user/dashboard")
.header("Authorization", "Bearer invalid-token"))
.andExpect(status().isUnauthorized());
}
}
6. 与其他测试注解对比
| 特性 | @AutoConfigureMockMvc | @WebMvcTest |
|---|---|---|
| 注解类型 | 配置注解 | 测试注解 |
| 使用方式 | 配合 @SpringBootTest | 单独使用 |
| 加载范围 | 完整应用上下文 | 只加载 Web 层 |
| 性能 | 较慢 | 较快 |
| 适用场景 | 集成测试 | 单元测试 |
@AutoConfigureMockMvc 使用:
java
@SpringBootTest
@AutoConfigureMockMvc
class FullIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private UserRepository userRepository; // 可以注入所有 Bean
@Test
void testWithFullContext() {
// 完整的集成测试
}
}
@WebMvcTest 使用:
java
@WebMvcTest(UserController.class)
class ControllerUnitTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService; // 只能 Mock Web 层依赖
@Test
void testControllerInIsolation() {
// Controller 层单元测试
}
}
7. 测试注解选择指南
┌─────────────────────────────────────────────────────────────┐
│ 测试注解选择流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 需要测试什么? │
│ ↓ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │Controller│ │Service │ │Repository│ │
│ └────┬────┘ └────┬────┘ └────┬────┘ │
│ ↓ ↓ ↓ │
│ @WebMvcTest @ExtendWith @DataJpaTest │
│ (MockBean) (Mockito) (H2 Database) │
│ ↓ ↓ ↓ │
│ ┌─────────────────────────────────────┐ │
│ │ 需要完整集成测试? │ │
│ └──────────────┬──────────────────────┘ │
│ ↓ │
│ @SpringBootTest + @AutoConfigureMockMvc │
│ │
└─────────────────────────────────────────────────────────────┘
十、测试策略对比
企业测试策略对比
| 测试方式 | 适用场景 | 优点 | 缺点 | 推荐度 |
|---|---|---|---|---|
| 单元测试 | 业务逻辑 | 快速、隔离 | 不测试集成 | ⭐⭐⭐⭐⭐ |
| 集成测试 | API接口 | 真实环境 | 较慢 | ⭐⭐⭐⭐ |
| Postman/Apifox | 手动验证 | 直观、易用 | 不自动化 | ⭐⭐⭐ |
| 自动化测试 | 回归测试 | 可重复 | 维护成本高 | ⭐⭐⭐⭐ |
| 性能测试 | 性能优化 | 发现瓶颈 | 复杂度高 | ⭐⭐⭐ |
推荐测试流程
开发阶段:
- 编写单元测试 → TDD 开发
- 本地运行测试 → IDE 快速验证
- Postman 手动测试 → 接口调试
提交阶段:
- 运行单元测试 → 确保代码质量
- 运行集成测试 → 验证功能完整性
- 代码覆盖率检查 → 达到标准要求
CI/CD 阶段:
- 自动化测试 → 所有测试通过
- API 自动化测试 → 接口回归测试
- 性能测试 → 定期执行
企业推荐测试组合
- 单元测试 (80%+) - JUnit + Mockito
- 集成测试 (60%+) - Spring Boot Test
- API测试 - Postman/Apifox + 自动化脚本
- 性能测试 - JMeter/Gatling (定期)
- CI/CD集成 - GitHub Actions/Jenkins
关键原则
- 测试金字塔:大量单元测试,适量集成测试,少量E2E测试
- 测试驱动:先写测试,再写代码
- 持续集成:每次提交都运行测试
- 覆盖率监控:确保代码质量
附录:常用测试注解速查表
| 注解 | 用途 | 适用层级 |
|---|---|---|
@SpringBootTest |
启动完整应用上下文 | 集成测试 |
@AutoConfigureMockMvc |
自动配置 MockMvc | Controller 集成测试 |
@WebMvcTest |
只加载 Web 层 | Controller 单元测试 |
@DataJpaTest |
只加载 JPA 相关组件 | Repository 测试 |
@ExtendWith(MockitoExtension.class) |
启用 Mockito | Service 单元测试 |
@Mock |
创建 Mock 对象 | 单元测试 |
@MockBean |
创建 Spring Mock Bean | 集成测试 |
@InjectMocks |
自动注入 Mock 对象 | 单元测试 |
@Transactional |
事务管理,测试后回滚 | 集成测试 |
@ActiveProfiles |
指定测试环境配置 | 所有测试 |
@BeforeEach |
每个测试前执行 | 所有测试 |
@AfterEach |
每个测试后执行 | 所有测试 |
@Sql |
执行 SQL 脚本 | 集成测试 |
总结
本指南涵盖了企业后端开发测试的所有重要方面:
- 测试层级体系:理解测试金字塔,合理分配测试资源
- 单元测试:使用 JUnit + Mockito 进行快速、隔离的测试
- 集成测试:使用 Spring Boot Test 进行真实的集成测试
- API 测试工具:Postman/Apifox/cURL 进行手动和自动化测试
- 自动化测试框架:TestRestTemplate/RestAssured 进行 API 自动化测试
- 性能测试:JMeter/Gatling 进行压力和性能测试
- 企业最佳实践:测试覆盖率、命名规范、数据管理、隔离策略
- CI/CD 集成:GitHub Actions/Jenkins 自动化测试流程
- @AutoConfigureMockMvc:深入理解 Spring Boot MVC 测试
- 测试策略对比:选择合适的测试方法和工具
通过遵循这些最佳实践,您可以构建一个高质量、可维护、可靠的测试体系,确保代码质量和系统稳定性。
文档版本 : 1.0
最后更新 : 2026-03-19
适用框架 : Spring Boot 3.x, Spring Security 6.x
测试框架: JUnit 5, Mockito, Spring Boot Test