SpringBoot后端开发测试全指南

Spring Boot 后端开发测试全指南

目录

  1. 测试层级体系
  2. 单元测试
  3. 集成测试
  4. [API 测试工具](#API 测试工具)
  5. 自动化测试框架
  6. 性能测试
  7. 企业测试最佳实践
  8. [CI/CD 集成测试](#CI/CD 集成测试)
  9. [@AutoConfigureMockMvc 详解](#@AutoConfigureMockMvc 详解)
  10. 测试策略对比

一、测试层级体系

企业中后端测试通常分为以下几个层级:

复制代码
┌─────────────────────────────────────────────────────────────┐
│                    测试金字塔                                 │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│                    ▲ 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 手动验证 直观、易用 不自动化 ⭐⭐⭐
自动化测试 回归测试 可重复 维护成本高 ⭐⭐⭐⭐
性能测试 性能优化 发现瓶颈 复杂度高 ⭐⭐⭐

推荐测试流程

开发阶段:
  1. 编写单元测试 → TDD 开发
  2. 本地运行测试 → IDE 快速验证
  3. Postman 手动测试 → 接口调试
提交阶段:
  1. 运行单元测试 → 确保代码质量
  2. 运行集成测试 → 验证功能完整性
  3. 代码覆盖率检查 → 达到标准要求
CI/CD 阶段:
  1. 自动化测试 → 所有测试通过
  2. API 自动化测试 → 接口回归测试
  3. 性能测试 → 定期执行

企业推荐测试组合

  1. 单元测试 (80%+) - JUnit + Mockito
  2. 集成测试 (60%+) - Spring Boot Test
  3. API测试 - Postman/Apifox + 自动化脚本
  4. 性能测试 - JMeter/Gatling (定期)
  5. 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 脚本 集成测试

总结

本指南涵盖了企业后端开发测试的所有重要方面:

  1. 测试层级体系:理解测试金字塔,合理分配测试资源
  2. 单元测试:使用 JUnit + Mockito 进行快速、隔离的测试
  3. 集成测试:使用 Spring Boot Test 进行真实的集成测试
  4. API 测试工具:Postman/Apifox/cURL 进行手动和自动化测试
  5. 自动化测试框架:TestRestTemplate/RestAssured 进行 API 自动化测试
  6. 性能测试:JMeter/Gatling 进行压力和性能测试
  7. 企业最佳实践:测试覆盖率、命名规范、数据管理、隔离策略
  8. CI/CD 集成:GitHub Actions/Jenkins 自动化测试流程
  9. @AutoConfigureMockMvc:深入理解 Spring Boot MVC 测试
  10. 测试策略对比:选择合适的测试方法和工具

通过遵循这些最佳实践,您可以构建一个高质量、可维护、可靠的测试体系,确保代码质量和系统稳定性。


文档版本 : 1.0
最后更新 : 2026-03-19
适用框架 : Spring Boot 3.x, Spring Security 6.x
测试框架: JUnit 5, Mockito, Spring Boot Test

相关推荐
大傻^2 小时前
Spring AI Alibaba MCP协议实战:模型上下文协议集成与工具调用
java·人工智能·后端·spring·elasticsearch·springaialibaba
稻草猫.2 小时前
MyBatis进阶:动态SQL与MyBatis Generator插件使用
java·数据库·后端·spring·mvc·mybatis
李白的粉2 小时前
基于springboot的在线问卷调查系统
java·spring boot·毕业设计·课程设计·源代码·在线问卷调查系统
程序员老乔2 小时前
Java 新纪元 — JDK 25 + Spring Boot 4 全栈实战(一):你的Java该升级了
java·spring boot·python
qq_256247052 小时前
Docker 部署 OpenClaw 踩坑实录:Web UI 访问、飞书配对及自定义模型配置
后端
困惑阿三2 小时前
全栈部署排雷手册:从 405 报错到飞书推送成功
服务器·前端·后端·nginx·阿里云·node.js·飞书
无名-CODING2 小时前
从零开始!Vue3+SpringBoot前后端分离项目Docker部署实战(下):Vue前端Nginx反代与致命坑点盘点
前端·spring boot·docker
bug攻城狮2 小时前
为什么 Spring Boot 要单元测试?
spring boot·后端·单元测试
iPadiPhone2 小时前
性能之基:Java IO 体系深度解析、面试陷阱与实战指南
java·开发语言·后端·面试