Spring Boot API文档与自动化测试详解

文章目录

    • [1. API文档概述](#1. API文档概述)
      • [1.1 API文档的重要性](#1.1 API文档的重要性)
      • [1.2 API文档工具](#1.2 API文档工具)
      • [1.3 核心依赖](#1.3 核心依赖)
    • [2. SpringDoc OpenAPI](#2. SpringDoc OpenAPI)
      • [2.1 基础配置](#2.1 基础配置)
      • [2.2 OpenAPI配置](#2.2 OpenAPI配置)
      • [2.3 API注解](#2.3 API注解)
      • [2.4 DTO文档注解](#2.4 DTO文档注解)
    • [3. 自动化测试](#3. 自动化测试)
      • [3.1 单元测试](#3.1 单元测试)
      • [3.2 集成测试](#3.2 集成测试)
      • [3.3 API测试](#3.3 API测试)
    • [4. 测试数据管理](#4. 测试数据管理)
      • [4.1 测试数据工厂](#4.1 测试数据工厂)
      • [4.2 测试配置](#4.2 测试配置)
    • [5. 性能测试](#5. 性能测试)
      • [5.1 负载测试](#5.1 负载测试)
      • [5.2 压力测试](#5.2 压力测试)
    • [6. 测试报告](#6. 测试报告)
      • [6.1 测试报告生成](#6.1 测试报告生成)
      • [6.2 测试配置](#6.2 测试配置)
    • [7. 总结](#7. 总结)

1. API文档概述

API文档是软件开发中的重要组成部分,通过完善的API文档可以提高开发效率、降低沟通成本、提升代码质量。Spring Boot提供了多种API文档生成和自动化测试解决方案。

1.1 API文档的重要性

  • 开发效率:减少API使用者的学习成本
  • 团队协作:统一API规范和接口定义
  • 代码质量:通过文档驱动开发提高代码质量
  • 维护性:便于API的维护和版本管理
  • 测试覆盖:确保API的完整性和正确性

1.2 API文档工具

  • Swagger/OpenAPI:最流行的API文档工具
  • SpringDoc OpenAPI:Spring Boot官方推荐的API文档工具
  • Postman:API测试和文档生成工具
  • Insomnia:API客户端和文档工具

1.3 核心依赖

xml 复制代码
<dependencies>
    <!-- SpringDoc OpenAPI -->
    <dependency>
        <groupId>org.springdoc</groupId>
        <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
        <version>2.0.2</version>
    </dependency>
    
    <!-- Spring Boot Test -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    
    <!-- TestContainers -->
    <dependency>
        <groupId>org.testcontainers</groupId>
        <artifactId>junit-jupiter</artifactId>
        <scope>test</scope>
    </dependency>
    
    <!-- WireMock -->
    <dependency>
        <groupId>com.github.tomakehurst</groupId>
        <artifactId>wiremock-jre8</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

2. SpringDoc OpenAPI

2.1 基础配置

yaml 复制代码
# application.yml
springdoc:
  api-docs:
    path: /api-docs
  swagger-ui:
    path: /swagger-ui.html
    operations-sorter: method
    tags-sorter: alpha
    try-it-out-enabled: true
  packages-to-scan: com.example.demo.controller
  paths-to-match: /api/**

2.2 OpenAPI配置

java 复制代码
package com.example.demo.config;

import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.servers.Server;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;

@Configuration
public class OpenAPIConfig {
    
    @Bean
    public OpenAPI customOpenAPI() {
        return new OpenAPI()
                .info(new Info()
                        .title("Spring Boot API")
                        .version("1.0.0")
                        .description("Spring Boot应用API文档")
                        .contact(new Contact()
                                .name("开发团队")
                                .email("dev@example.com")
                                .url("https://example.com"))
                        .license(new License()
                                .name("MIT License")
                                .url("https://opensource.org/licenses/MIT")))
                .servers(List.of(
                        new Server().url("http://localhost:8080").description("开发环境"),
                        new Server().url("https://api.example.com").description("生产环境")
                ));
    }
}

2.3 API注解

java 复制代码
package com.example.demo.controller;

import com.example.demo.dto.UserDto;
import com.example.demo.entity.User;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/users")
@Tag(name = "用户管理", description = "用户相关API")
public class UserController {
    
    @Operation(summary = "获取用户列表", description = "分页获取用户列表")
    @ApiResponses(value = {
            @ApiResponse(responseCode = "200", description = "成功获取用户列表",
                    content = @Content(schema = @Schema(implementation = UserDto.class))),
            @ApiResponse(responseCode = "400", description = "请求参数错误"),
            @ApiResponse(responseCode = "500", description = "服务器内部错误")
    })
    @GetMapping
    public ResponseEntity<List<UserDto>> getUsers(
            @Parameter(description = "页码", example = "0") @RequestParam(defaultValue = "0") int page,
            @Parameter(description = "每页大小", example = "10") @RequestParam(defaultValue = "10") int size) {
        // 实现获取用户列表逻辑
        return ResponseEntity.ok(null);
    }
    
    @Operation(summary = "根据ID获取用户", description = "根据用户ID获取用户详细信息")
    @ApiResponses(value = {
            @ApiResponse(responseCode = "200", description = "成功获取用户信息",
                    content = @Content(schema = @Schema(implementation = UserDto.class))),
            @ApiResponse(responseCode = "404", description = "用户不存在"),
            @ApiResponse(responseCode = "500", description = "服务器内部错误")
    })
    @GetMapping("/{id}")
    public ResponseEntity<UserDto> getUserById(
            @Parameter(description = "用户ID", example = "1") @PathVariable Long id) {
        // 实现根据ID获取用户逻辑
        return ResponseEntity.ok(null);
    }
    
    @Operation(summary = "创建用户", description = "创建新用户")
    @ApiResponses(value = {
            @ApiResponse(responseCode = "201", description = "用户创建成功",
                    content = @Content(schema = @Schema(implementation = UserDto.class))),
            @ApiResponse(responseCode = "400", description = "请求参数错误"),
            @ApiResponse(responseCode = "409", description = "用户已存在"),
            @ApiResponse(responseCode = "500", description = "服务器内部错误")
    })
    @PostMapping
    public ResponseEntity<UserDto> createUser(
            @Parameter(description = "用户信息") @RequestBody UserDto userDto) {
        // 实现创建用户逻辑
        return ResponseEntity.ok(null);
    }
    
    @Operation(summary = "更新用户", description = "更新用户信息")
    @ApiResponses(value = {
            @ApiResponse(responseCode = "200", description = "用户更新成功",
                    content = @Content(schema = @Schema(implementation = UserDto.class))),
            @ApiResponse(responseCode = "400", description = "请求参数错误"),
            @ApiResponse(responseCode = "404", description = "用户不存在"),
            @ApiResponse(responseCode = "500", description = "服务器内部错误")
    })
    @PutMapping("/{id}")
    public ResponseEntity<UserDto> updateUser(
            @Parameter(description = "用户ID", example = "1") @PathVariable Long id,
            @Parameter(description = "用户信息") @RequestBody UserDto userDto) {
        // 实现更新用户逻辑
        return ResponseEntity.ok(null);
    }
    
    @Operation(summary = "删除用户", description = "根据用户ID删除用户")
    @ApiResponses(value = {
            @ApiResponse(responseCode = "204", description = "用户删除成功"),
            @ApiResponse(responseCode = "404", description = "用户不存在"),
            @ApiResponse(responseCode = "500", description = "服务器内部错误")
    })
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(
            @Parameter(description = "用户ID", example = "1") @PathVariable Long id) {
        // 实现删除用户逻辑
        return ResponseEntity.noContent().build();
    }
}

2.4 DTO文档注解

java 复制代码
package com.example.demo.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;

@Schema(description = "用户信息")
public class UserDto {
    
    @Schema(description = "用户ID", example = "1")
    private Long id;
    
    @Schema(description = "用户名", example = "john_doe", required = true)
    @NotBlank(message = "用户名不能为空")
    @Size(min = 2, max = 20, message = "用户名长度必须在2-20个字符之间")
    private String username;
    
    @Schema(description = "邮箱地址", example = "john@example.com", required = true)
    @NotBlank(message = "邮箱不能为空")
    @Email(message = "邮箱格式不正确")
    private String email;
    
    @Schema(description = "全名", example = "John Doe")
    private String fullName;
    
    @Schema(description = "手机号", example = "13800138000")
    private String phoneNumber;
    
    @Schema(description = "用户状态", example = "ACTIVE")
    private String status;
    
    @Schema(description = "创建时间", example = "2023-01-01T00:00:00")
    private LocalDateTime createdAt;
    
    // 构造方法
    public UserDto() {}
    
    public UserDto(Long id, String username, String email, String fullName) {
        this.id = id;
        this.username = username;
        this.email = email;
        this.fullName = fullName;
    }
    
    // getter和setter方法
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    
    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }
    
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
    
    public String getFullName() { return fullName; }
    public void setFullName(String fullName) { this.fullName = fullName; }
    
    public String getPhoneNumber() { return phoneNumber; }
    public void setPhoneNumber(String phoneNumber) { this.phoneNumber = phoneNumber; }
    
    public String getStatus() { return status; }
    public void setStatus(String status) { this.status = status; }
    
    public LocalDateTime getCreatedAt() { return createdAt; }
    public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
}

3. 自动化测试

3.1 单元测试

java 复制代码
package com.example.demo.service;

import com.example.demo.entity.User;
import com.example.demo.repository.UserRepository;
import com.example.demo.service.UserService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
class UserServiceTest {
    
    @Mock
    private UserRepository userRepository;
    
    @InjectMocks
    private UserService userService;
    
    private User testUser;
    
    @BeforeEach
    void setUp() {
        testUser = new User();
        testUser.setId(1L);
        testUser.setUsername("testuser");
        testUser.setEmail("test@example.com");
        testUser.setFullName("Test User");
    }
    
    @Test
    void testCreateUser() {
        // Given
        when(userRepository.save(any(User.class))).thenReturn(testUser);
        
        // When
        User result = userService.createUser(testUser);
        
        // Then
        assertNotNull(result);
        assertEquals("testuser", result.getUsername());
        verify(userRepository, times(1)).save(testUser);
    }
    
    @Test
    void testGetUserById() {
        // Given
        when(userRepository.findById(1L)).thenReturn(Optional.of(testUser));
        
        // When
        Optional<User> result = userService.getUserById(1L);
        
        // Then
        assertTrue(result.isPresent());
        assertEquals("testuser", result.get().getUsername());
        verify(userRepository, times(1)).findById(1L);
    }
    
    @Test
    void testGetUserByIdNotFound() {
        // Given
        when(userRepository.findById(999L)).thenReturn(Optional.empty());
        
        // When
        Optional<User> result = userService.getUserById(999L);
        
        // Then
        assertFalse(result.isPresent());
        verify(userRepository, times(1)).findById(999L);
    }
    
    @Test
    void testUpdateUser() {
        // Given
        User updatedUser = new User();
        updatedUser.setId(1L);
        updatedUser.setUsername("updateduser");
        updatedUser.setEmail("updated@example.com");
        
        when(userRepository.findById(1L)).thenReturn(Optional.of(testUser));
        when(userRepository.save(any(User.class))).thenReturn(updatedUser);
        
        // When
        User result = userService.updateUser(1L, updatedUser);
        
        // Then
        assertNotNull(result);
        assertEquals("updateduser", result.getUsername());
        verify(userRepository, times(1)).findById(1L);
        verify(userRepository, times(1)).save(any(User.class));
    }
    
    @Test
    void testDeleteUser() {
        // Given
        when(userRepository.existsById(1L)).thenReturn(true);
        
        // When
        userService.deleteUser(1L);
        
        // Then
        verify(userRepository, times(1)).existsById(1L);
        verify(userRepository, times(1)).deleteById(1L);
    }
}

3.2 集成测试

java 复制代码
package com.example.demo.controller;

import com.example.demo.dto.UserDto;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureWebMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.transaction.annotation.Transactional;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@SpringBootTest
@AutoConfigureWebMvc
@ActiveProfiles("test")
@Transactional
class UserControllerIntegrationTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @Autowired
    private ObjectMapper objectMapper;
    
    private UserDto testUserDto;
    
    @BeforeEach
    void setUp() {
        testUserDto = new UserDto();
        testUserDto.setUsername("testuser");
        testUserDto.setEmail("test@example.com");
        testUserDto.setFullName("Test User");
    }
    
    @Test
    void testCreateUser() throws Exception {
        mockMvc.perform(post("/api/users")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(testUserDto)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.username").value("testuser"))
                .andExpect(jsonPath("$.email").value("test@example.com"));
    }
    
    @Test
    void testGetUsers() throws Exception {
        mockMvc.perform(get("/api/users")
                .param("page", "0")
                .param("size", "10"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.content").isArray());
    }
    
    @Test
    void testGetUserById() throws Exception {
        // 先创建用户
        String response = mockMvc.perform(post("/api/users")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(testUserDto)))
                .andExpect(status().isOk())
                .andReturn()
                .getResponse()
                .getContentAsString();
        
        UserDto createdUser = objectMapper.readValue(response, UserDto.class);
        
        // 获取用户
        mockMvc.perform(get("/api/users/" + createdUser.getId()))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.username").value("testuser"));
    }
    
    @Test
    void testUpdateUser() throws Exception {
        // 先创建用户
        String response = mockMvc.perform(post("/api/users")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(testUserDto)))
                .andExpect(status().isOk())
                .andReturn()
                .getResponse()
                .getContentAsString();
        
        UserDto createdUser = objectMapper.readValue(response, UserDto.class);
        
        // 更新用户
        createdUser.setFullName("Updated User");
        mockMvc.perform(put("/api/users/" + createdUser.getId())
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(createdUser)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.fullName").value("Updated User"));
    }
    
    @Test
    void testDeleteUser() throws Exception {
        // 先创建用户
        String response = mockMvc.perform(post("/api/users")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(testUserDto)))
                .andExpect(status().isOk())
                .andReturn()
                .getResponse()
                .getContentAsString();
        
        UserDto createdUser = objectMapper.readValue(response, UserDto.class);
        
        // 删除用户
        mockMvc.perform(delete("/api/users/" + createdUser.getId()))
                .andExpect(status().isNoContent());
    }
}

3.3 API测试

java 复制代码
package com.example.demo.test;

import com.example.demo.dto.UserDto;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureWebMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@SpringBootTest
@AutoConfigureWebMvc
@ActiveProfiles("test")
class APITest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @Autowired
    private ObjectMapper objectMapper;
    
    private UserDto testUserDto;
    
    @BeforeEach
    void setUp() {
        testUserDto = new UserDto();
        testUserDto.setUsername("testuser");
        testUserDto.setEmail("test@example.com");
        testUserDto.setFullName("Test User");
    }
    
    @Test
    void testUserCRUD() throws Exception {
        // 创建用户
        MvcResult createResult = mockMvc.perform(post("/api/users")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(testUserDto)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.username").value("testuser"))
                .andReturn();
        
        UserDto createdUser = objectMapper.readValue(
                createResult.getResponse().getContentAsString(), UserDto.class);
        
        // 获取用户
        mockMvc.perform(get("/api/users/" + createdUser.getId()))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.username").value("testuser"));
        
        // 更新用户
        createdUser.setFullName("Updated User");
        mockMvc.perform(put("/api/users/" + createdUser.getId())
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(createdUser)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.fullName").value("Updated User"));
        
        // 删除用户
        mockMvc.perform(delete("/api/users/" + createdUser.getId()))
                .andExpect(status().isNoContent());
    }
    
    @Test
    void testValidation() throws Exception {
        // 测试用户名验证
        UserDto invalidUser = new UserDto();
        invalidUser.setUsername(""); // 空用户名
        invalidUser.setEmail("invalid-email"); // 无效邮箱
        
        mockMvc.perform(post("/api/users")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(invalidUser)))
                .andExpect(status().isBadRequest());
    }
    
    @Test
    void testErrorHandling() throws Exception {
        // 测试404错误
        mockMvc.perform(get("/api/users/999"))
                .andExpect(status().isNotFound());
    }
}

4. 测试数据管理

4.1 测试数据工厂

java 复制代码
package com.example.demo.test;

import com.example.demo.entity.User;
import com.example.demo.dto.UserDto;
import java.time.LocalDateTime;
import java.util.List;
import java.util.ArrayList;

public class TestDataFactory {
    
    public static User createUser() {
        User user = new User();
        user.setUsername("testuser");
        user.setEmail("test@example.com");
        user.setFullName("Test User");
        user.setPhoneNumber("13800138000");
        user.setStatus("ACTIVE");
        user.setCreatedAt(LocalDateTime.now());
        return user;
    }
    
    public static User createUser(String username, String email) {
        User user = createUser();
        user.setUsername(username);
        user.setEmail(email);
        return user;
    }
    
    public static UserDto createUserDto() {
        UserDto userDto = new UserDto();
        userDto.setUsername("testuser");
        userDto.setEmail("test@example.com");
        userDto.setFullName("Test User");
        userDto.setPhoneNumber("13800138000");
        userDto.setStatus("ACTIVE");
        return userDto;
    }
    
    public static UserDto createUserDto(String username, String email) {
        UserDto userDto = createUserDto();
        userDto.setUsername(username);
        userDto.setEmail(email);
        return userDto;
    }
    
    public static List<User> createUserList(int count) {
        List<User> users = new ArrayList<>();
        for (int i = 0; i < count; i++) {
            users.add(createUser("user" + i, "user" + i + "@example.com"));
        }
        return users;
    }
    
    public static List<UserDto> createUserDtoList(int count) {
        List<UserDto> userDtos = new ArrayList<>();
        for (int i = 0; i < count; i++) {
            userDtos.add(createUserDto("user" + i, "user" + i + "@example.com"));
        }
        return userDtos;
    }
}

4.2 测试配置

java 复制代码
package com.example.demo.test;

import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;

@TestConfiguration
public class TestConfig {
    
    @Bean
    @Primary
    public PasswordEncoder testPasswordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }
}

5. 性能测试

5.1 负载测试

java 复制代码
package com.example.demo.test;

import com.example.demo.dto.UserDto;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureWebMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.List;
import java.util.ArrayList;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@SpringBootTest
@AutoConfigureWebMvc
@ActiveProfiles("test")
class LoadTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @Autowired
    private ObjectMapper objectMapper;
    
    @Test
    void testConcurrentRequests() throws Exception {
        ExecutorService executor = Executors.newFixedThreadPool(10);
        List<Future<?>> futures = new ArrayList<>();
        
        // 发送100个并发请求
        for (int i = 0; i < 100; i++) {
            Future<?> future = executor.submit(() -> {
                try {
                    mockMvc.perform(get("/api/users"))
                            .andExpect(status().isOk());
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
            futures.add(future);
        }
        
        // 等待所有请求完成
        for (Future<?> future : futures) {
            future.get(5, TimeUnit.SECONDS);
        }
        
        executor.shutdown();
    }
    
    @Test
    void testResponseTime() throws Exception {
        long startTime = System.currentTimeMillis();
        
        mockMvc.perform(get("/api/users"))
                .andExpect(status().isOk());
        
        long endTime = System.currentTimeMillis();
        long responseTime = endTime - startTime;
        
        assert responseTime < 1000; // 响应时间应小于1秒
    }
}

5.2 压力测试

java 复制代码
package com.example.demo.test;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureWebMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@SpringBootTest
@AutoConfigureWebMvc
@ActiveProfiles("test")
class StressTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @Test
    void testHighLoad() throws Exception {
        ExecutorService executor = Executors.newFixedThreadPool(50);
        AtomicInteger successCount = new AtomicInteger(0);
        AtomicInteger errorCount = new AtomicInteger(0);
        
        // 发送1000个请求
        for (int i = 0; i < 1000; i++) {
            executor.submit(() -> {
                try {
                    mockMvc.perform(get("/api/users"))
                            .andExpect(status().isOk());
                    successCount.incrementAndGet();
                } catch (Exception e) {
                    errorCount.incrementAndGet();
                }
            });
        }
        
        executor.shutdown();
        executor.awaitTermination(30, TimeUnit.SECONDS);
        
        System.out.println("成功请求数: " + successCount.get());
        System.out.println("失败请求数: " + errorCount.get());
        
        assert successCount.get() > 900; // 成功率应大于90%
    }
}

6. 测试报告

6.1 测试报告生成

java 复制代码
package com.example.demo.test;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureWebMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.beans.factory.annotation.Autowired;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@SpringBootTest
@AutoConfigureWebMvc
@ActiveProfiles("test")
class TestReport {
    
    @Autowired
    private MockMvc mockMvc;
    
    @Test
    void testAPICoverage() throws Exception {
        // 测试所有API端点
        mockMvc.perform(get("/api/users"))
                .andExpect(status().isOk());
        
        mockMvc.perform(get("/api/users/1"))
                .andExpect(status().isOk());
        
        mockMvc.perform(post("/api/users")
                .contentType("application/json")
                .content("{\"username\":\"test\",\"email\":\"test@example.com\"}"))
                .andExpect(status().isOk());
        
        mockMvc.perform(put("/api/users/1")
                .contentType("application/json")
                .content("{\"username\":\"test\",\"email\":\"test@example.com\"}"))
                .andExpect(status().isOk());
        
        mockMvc.perform(delete("/api/users/1"))
                .andExpect(status().isNoContent());
    }
}

6.2 测试配置

yaml 复制代码
# application-test.yml
spring:
  datasource:
    url: jdbc:h2:mem:testdb
    driver-class-name: org.h2.Driver
    username: sa
    password: 
  
  jpa:
    hibernate:
      ddl-auto: create-drop
    show-sql: false
    properties:
      hibernate:
        format_sql: false

logging:
  level:
    com.example.demo: DEBUG
    org.springframework.web: WARN

7. 总结

Spring Boot API文档与自动化测试提供了完整的API开发解决方案:

  1. API文档:SpringDoc OpenAPI、Swagger UI、API注解
  2. 自动化测试:单元测试、集成测试、API测试
  3. 测试数据管理:测试数据工厂、测试配置
  4. 性能测试:负载测试、压力测试、响应时间测试
  5. 测试报告:测试覆盖率、测试结果分析

通过完善的API文档和自动化测试,可以确保API的质量和可靠性。


相关推荐
vx Biye_Design4 小时前
servlet宠物医院管理系统-计算机毕业设计源码77418
java·vue.js·spring·servlet·eclipse·mybatis
数据小馒头4 小时前
Web原生架构 vs 传统C/S架构:在数据库管理中的性能与安全差异
后端
用户68545375977694 小时前
🔑 AQS抽象队列同步器:Java并发编程的"万能钥匙"
后端
照物华4 小时前
构建优雅的 Spring Boot Starter:Bean 注册与属性绑定的两大机制
java·spring boot
pcm1235674 小时前
内置线程池的核心参数分析配置
java·开发语言
yren4 小时前
Mysql 多版本并发控制 MVCC
后端
回家路上绕了弯4 小时前
外卖员重复抢单?从技术到运营的全链路解决方案
分布式·后端
考虑考虑4 小时前
解决idea导入项目出现不了maven
java·后端·maven
代码不停4 小时前
JavaEE初级 多线程案例(单例模式、阻塞队列、线程池、定时器)
java·开发语言·单例模式·java-ee