文章目录
-
- [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开发解决方案:
- API文档:SpringDoc OpenAPI、Swagger UI、API注解
- 自动化测试:单元测试、集成测试、API测试
- 测试数据管理:测试数据工厂、测试配置
- 性能测试:负载测试、压力测试、响应时间测试
- 测试报告:测试覆盖率、测试结果分析
通过完善的API文档和自动化测试,可以确保API的质量和可靠性。