第2471篇:AI驱动的API测试——自动生成和维护API测试用例
2026/4/30大约 6 分钟
第2471篇:AI驱动的API测试——自动生成和维护API测试用例
适读人群:后端工程师、测试工程师 | 阅读时长:约16分钟 | 核心价值:用LLM基于OpenAPI规范自动生成全面的API测试用例,并实现测试的自动维护
我见过最糟糕的API测试状态:一个Postman collection,200个请求,所有请求里的token都是3个月前手动填进去的,其中一半的请求打开来是红色的(请求失败),另一半有没有assertions全靠缘分。
这还是"有测试"的情况。更多的情况是:API没有任何自动化测试,测试全靠人工点,每次发版前测试人员要花一天手动过一遍主要功能。
这两种情况都说明一个问题:API测试的维护成本太高,所以没有被认真对待。
AI可以帮助降低这个成本。
AI驱动API测试的核心价值
- 基于OpenAPI规范自动生成测试用例:不需要手动写,给它接口定义它就能生成测试
- 覆盖边界情况:正常路径、缺少必填字段、非法格式、权限问题——AI会系统性地生成
- 自动维护:接口定义变了,自动更新对应的测试用例
- 错误场景的智能生成:不只是happy path,AI能理解业务逻辑并生成有意义的负向测试
系统设计
核心实现
1. OpenAPI解析器
@Component
public class OpenAPISpecAnalyzer {
private final OpenAPIParser openAPIParser;
public List<APIEndpoint> analyzeSpec(String specContent) {
SwaggerParseResult parseResult = openAPIParser.readContents(specContent, null, null);
OpenAPI openAPI = parseResult.getOpenAPI();
List<APIEndpoint> endpoints = new ArrayList<>();
openAPI.getPaths().forEach((path, pathItem) -> {
// 处理每个HTTP方法
processOperation(path, "GET", pathItem.getGet(), openAPI, endpoints);
processOperation(path, "POST", pathItem.getPost(), openAPI, endpoints);
processOperation(path, "PUT", pathItem.getPut(), openAPI, endpoints);
processOperation(path, "PATCH", pathItem.getPatch(), openAPI, endpoints);
processOperation(path, "DELETE", pathItem.getDelete(), openAPI, endpoints);
});
return endpoints;
}
private void processOperation(
String path, String method, Operation operation,
OpenAPI openAPI, List<APIEndpoint> endpoints) {
if (operation == null) return;
// 解析路径参数
List<ParameterInfo> pathParams = extractParameters(
operation.getParameters(), "path", openAPI
);
// 解析查询参数
List<ParameterInfo> queryParams = extractParameters(
operation.getParameters(), "query", openAPI
);
// 解析请求体
RequestBodyInfo requestBody = null;
if (operation.getRequestBody() != null) {
requestBody = extractRequestBody(operation.getRequestBody(), openAPI);
}
// 解析响应
Map<String, ResponseInfo> responses = extractResponses(operation.getResponses(), openAPI);
// 安全要求(认证)
List<String> securitySchemes = extractSecurityRequirements(operation, openAPI);
endpoints.add(APIEndpoint.builder()
.path(path)
.method(method)
.operationId(operation.getOperationId())
.summary(operation.getSummary())
.description(operation.getDescription())
.pathParams(pathParams)
.queryParams(queryParams)
.requestBody(requestBody)
.responses(responses)
.securitySchemes(securitySchemes)
.build());
}
private RequestBodyInfo extractRequestBody(RequestBody requestBody, OpenAPI openAPI) {
Content content = requestBody.getContent();
MediaType jsonContent = content.get("application/json");
if (jsonContent == null) return null;
Schema<?> schema = resolveSchema(jsonContent.getSchema(), openAPI);
return RequestBodyInfo.builder()
.required(requestBody.getRequired())
.schema(schema)
.schemaDescription(buildSchemaDescription(schema, openAPI))
.build();
}
}2. 测试用例生成器
@Service
public class APITestCaseGenerator {
private final ChatClient chatClient;
private final TestDataGenerator testDataGenerator;
public List<APITestCase> generateTestCases(APIEndpoint endpoint) {
// 生成测试数据
TestDataSet testData = testDataGenerator.generate(endpoint);
// 让LLM规划测试场景
List<TestScenario> scenarios = planTestScenarios(endpoint, testData);
// 为每个场景生成具体的测试代码
return scenarios.stream()
.map(scenario -> generateTestCode(endpoint, scenario, testData))
.collect(toList());
}
private List<TestScenario> planTestScenarios(APIEndpoint endpoint, TestDataSet testData) {
String planningPrompt = """
分析以下API端点,规划需要覆盖的测试场景:
端点: %s %s
描述: %s
路径参数: %s
查询参数: %s
请求体字段: %s
预期响应:
%s
请列出需要测试的场景,包括:
1. 正常路径(happy path)
2. 每个必填参数为null/空的情况
3. 每个参数的边界值
4. 权限相关(未认证、无权限)
5. 数据不存在的情况(404)
6. 业务逻辑错误的情况(业务规则违反)
返回JSON:
{
"scenarios": [
{
"name": "场景名",
"description": "场景描述",
"type": "HAPPY_PATH/INVALID_INPUT/AUTHORIZATION/NOT_FOUND/BUSINESS_ERROR",
"expectedStatusCode": 200,
"modifications": "对正常测试数据做哪些修改(如果是负向测试)"
}
]
}
""".formatted(
endpoint.getMethod(), endpoint.getPath(),
endpoint.getDescription(),
describeParams(endpoint.getPathParams()),
describeParams(endpoint.getQueryParams()),
describeRequestBody(endpoint.getRequestBody()),
describeResponses(endpoint.getResponses())
);
ChatResponse response = chatClient.call(new Prompt(new UserMessage(planningPrompt)));
return parseTestScenarios(response.getResult().getOutput().getContent());
}
private APITestCase generateTestCode(
APIEndpoint endpoint,
TestScenario scenario,
TestDataSet testData) {
String codePrompt = """
为以下API测试场景生成RestAssured测试代码:
端点: %s %s
测试场景: %s
场景描述: %s
预期状态码: %d
测试数据修改: %s
测试数据(正常情况下的合法数据):
%s
要求:
1. 使用RestAssured + JUnit5框架
2. 添加@DisplayName注解
3. 对响应做有意义的断言(不只是验证状态码)
4. 如果是认证相关测试,使用预置的测试token常量
5. 测试方法要独立,不依赖其他测试的状态
只返回Java测试方法代码(不包含类定义)。
""".formatted(
endpoint.getMethod(), endpoint.getPath(),
scenario.getName(), scenario.getDescription(),
scenario.getExpectedStatusCode(),
scenario.getModifications(),
testData.toJson()
);
ChatResponse response = chatClient.call(new Prompt(new UserMessage(codePrompt)));
return APITestCase.builder()
.endpoint(endpoint)
.scenario(scenario)
.testCode(extractMethodCode(response.getResult().getOutput().getContent()))
.build();
}
}3. 测试数据智能生成
@Service
public class TestDataGenerator {
private final ChatClient chatClient;
/**
* 基于Schema生成有意义的测试数据(不是随机数据)
* AI生成的数据更符合业务语义
*/
public TestDataSet generate(APIEndpoint endpoint) {
if (endpoint.getRequestBody() == null) {
return TestDataSet.empty();
}
String schemaDescription = endpoint.getRequestBody().getSchemaDescription();
String prompt = """
为以下API请求体生成测试数据:
端点: %s %s
请求体Schema:
%s
请生成:
1. 一组完整的合法测试数据(所有必填字段都有,值符合业务语义)
2. 各个必填字段为空时的数据集
3. 各个字段超过最大长度/最大值的数据集
生成的数据要符合业务场景,不要用"test123"这类无意义的测试数据。
比如name字段要用真实的人名,email要用合法格式的邮箱,金额要用合理的业务数值。
返回JSON格式的测试数据集。
""".formatted(
endpoint.getMethod(), endpoint.getPath(), schemaDescription
);
ChatResponse response = chatClient.call(new Prompt(new UserMessage(prompt)));
return parseTestDataSet(response.getResult().getOutput().getContent());
}
}4. 完整测试类生成
把所有测试用例组装成一个完整的测试类:
@Service
public class APITestClassGenerator {
public String generateTestClass(
APIEndpoint endpoint,
List<APITestCase> testCases,
TestConfiguration config) {
StringBuilder classCode = new StringBuilder();
// 类头
classCode.append("package ").append(config.getTestPackage()).append(";\n\n");
classCode.append(IMPORTS).append("\n\n");
classCode.append("@TestMethodOrder(MethodOrderer.OrderAnnotation.class)\n");
classCode.append("@DisplayName(\"").append(endpoint.getSummary()).append(" API Tests\")\n");
classCode.append("class ").append(generateClassName(endpoint)).append(" {\n\n");
// 配置
classCode.append("""
private static final String BASE_URL = System.getProperty("test.api.baseUrl", "http://localhost:8080");
private static final String TEST_TOKEN = System.getProperty("test.api.token", "test-token");
@BeforeEach
void setUp() {
RestAssured.baseURI = BASE_URL;
RestAssured.filters(new RequestLoggingFilter(), new ResponseLoggingFilter());
}
""");
// 测试方法
int order = 1;
for (APITestCase testCase : testCases) {
classCode.append(" @Test\n");
classCode.append(" @Order(").append(order++).append(")\n");
classCode.append(indent(testCase.getTestCode(), 4)).append("\n\n");
}
classCode.append("}\n");
return classCode.toString();
}
private static final String IMPORTS = """
import io.restassured.RestAssured;
import io.restassured.filter.log.RequestLoggingFilter;
import io.restassured.filter.log.ResponseLoggingFilter;
import io.restassured.http.ContentType;
import org.junit.jupiter.api.*;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.*;
""";
}自动维护:接口变了,测试自动更新
最有价值的功能是测试的自动维护:
@Service
public class TestMaintenanceService {
private final OpenAPISpecAnalyzer specAnalyzer;
private final APITestCaseGenerator testGenerator;
/**
* 对比新旧OpenAPI规范,识别变更,更新受影响的测试
*/
public MaintenanceReport updateTests(
String oldSpec, String newSpec,
Path testDirectory) {
List<APIEndpoint> oldEndpoints = specAnalyzer.analyzeSpec(oldSpec);
List<APIEndpoint> newEndpoints = specAnalyzer.analyzeSpec(newSpec);
// 识别变更
APISpecDiff diff = computeDiff(oldEndpoints, newEndpoints);
MaintenanceReport.Builder report = MaintenanceReport.builder();
// 新增的端点:生成新测试
for (APIEndpoint newEndpoint : diff.getAddedEndpoints()) {
List<APITestCase> newTests = testGenerator.generateTestCases(newEndpoint);
writeTestClass(newEndpoint, newTests, testDirectory);
report.addedTest(newEndpoint.getOperationId());
}
// 修改的端点:更新测试
for (APIEndpointChange change : diff.getModifiedEndpoints()) {
updateTestsForChangedEndpoint(change, testDirectory, report);
}
// 删除的端点:删除对应测试
for (APIEndpoint removedEndpoint : diff.getRemovedEndpoints()) {
deleteTestClass(removedEndpoint, testDirectory);
report.removedTest(removedEndpoint.getOperationId());
}
return report.build();
}
}工程经验
关于测试数据的真实性:AI生成的测试数据比人手写的更接近真实业务数据,但有些情况下还是需要手动补充——比如需要测试数据库里已有的特定记录、需要特定业务状态下的数据等。我们的做法是维护一个"seed数据文件",AI生成的测试可以从这个文件里引用真实的ID和数据。
关于测试的独立性:生成的测试要注意幂等性,特别是POST/PUT/DELETE类型的测试。如果每次运行都往数据库写数据,多次运行之后数据库里就有一堆垃圾数据了。建议在测试中加@Transactional回滚,或者用专门的测试数据库并在每次测试后清理。
