我用 AI 把 Swagger 变成 500 条 pytest 用例,回归从 3 天到 3 小时
上个月我把订单中台的回归测试从 Postman 里拽了出来。之前 87 个接口,每次发版前手工点,3 天起步,点到后面人都麻了。现在 CI 上跑 500 多条 pytest,全量回归 3 小时出头,MR 只跑 smoke 的话 20 分钟。
核心不是哪个神器,而是把 Swagger 当成数据源,让 AI 补测试场景,再让 pytest 去干重复劳动。下面是我实际落地的过程,代码是删减过的,但骨架能直接抄。
先别碰 AI,把 Swagger 弄干净
我一开始也想着直接拿 Swagger 喂给模型,结果生成一堆不存在的字段。后来发现,问题不在 AI,在 Swagger 本身。
我们要求每个接口必须满足:
- OpenAPI 3.0,别拿 Swagger 2.0 凑合;
- operationId 唯一,后面用例 ID 靠它;
- 每个 schema 字段尽量有 example;
- 必填字段、枚举、最大最小长度写清楚;
- 错误响应别只写个 400,把业务错误码也列出来。
校验命令我放在 pre-commit 里:
openapi-spec-validator openapi.yaml
这一步很枯燥,但省不掉。Swagger 越像合同,后面生成的用例越像回事。
把接口抽成“用例原材料”
先写个脚本,把 OpenAPI 里的 operation 拉平。我不直接生成测试文件,先生成一份 YAML,方便人工审核。
# tools/extract_ops.py
import yaml
HTTP_METHODS = {"get", "post", "put", "patch", "delete"}
def extract_operations(spec):
for path, methods in spec["paths"].items():
for method, op in methods.items():
if method not in HTTP_METHODS:
continue
yield {
"id": op.get("operationId") or f"{method}_{path}".replace("/", "_"),
"method": method.upper(),
"path": path,
"summary": op.get("summary", ""),
"description": op.get("description", ""),
"parameters": op.get("parameters", []),
"requestBody": op.get("requestBody", {}),
"responses": op.get("responses", {}),
}
if __name__ == "__main__":
with open("openapi.yaml", encoding="utf-8") as f:
spec = yaml.safe_load(f)
ops = list(extract_operations(spec))
with open("cases/raw_ops.yaml", "w", encoding="utf-8") as f:
yaml.safe_dump(ops, f, allow_unicode=True, sort_keys=False)
print(f"抽到 {len(ops)} 个 operation")
我们 87 个接口,抽出来 87 条。但这只是原材料,不是用例。
让 AI 干它擅长的:想场景
接口测试最烦的不是发请求,是想“还要测什么”。正常流谁都会写,缺参、类型错、边界值、权限、幂等,这些才费脑子。
我把每个 operation 的 JSON 发给模型,要求只输出 JSON,不要解释。提示词大概是这样:
你是一个接口测试专家。下面是一个 OpenAPI operation 的 JSON。
请生成 pytest 参数化用例,输出 JSON 数组,每个元素包含:
{
"id": "唯一ID",
"name": "用例名",
"path_params": {},
"query": {},
"headers": {},
"body": {},
"expected_status": [200],
"asserts": [
{"type": "jsonpath", "path": "$.code", "eq": 0}
],
"needs_review": false
}
规则:
1. 只根据给定 schema 生成,不要发明字段;
2. 每个接口至少覆盖:正常、必填缺失、类型错误、边界值、鉴权缺失;
3. 没有 example 的字段可以用合理值,但 needs_review 标 true;
4. 写接口不要生成真实删除、支付、退款这类危险操作;
5. 不确定的断言不要硬写,标 needs_review。
调用代码:
# tools/ai_enrich.py
import json, os, yaml
from openai import OpenAI
client = OpenAI(
api_key=os.getenv("LLM_API_KEY"),
base_url=os.getenv("LLM_BASE_URL"),
)
PROMPT = open("prompts/gen_cases.txt", encoding="utf-8").read()
def enrich(op):
resp = client.chat.completions.create(
model=os.getenv("LLM_MODEL", "gpt-4o-mini"),
temperature=0.2,
messages=[
{"role": "system", "content": "你只输出 JSON,不要 Markdown,不要解释。"},
{"role": "user", "content": PROMPT + "\n\nOperation:\n" + json.dumps(op, ensure_ascii=False)},
],
)
return json.loads(resp.choices[0].message.content)
with open("cases/raw_ops.yaml", encoding="utf-8") as f:
ops = yaml.safe_load(f)
all_cases = []
for op in ops:
cases = enrich(op)
for c in cases:
c["operation_id"] = op["id"]
c["method"] = op["method"]
c["path"] = op["path"]
all_cases.extend(cases)
with open("cases/generated.yaml", "w", encoding="utf-8") as f:
yaml.safe_dump(all_cases, f, allow_unicode=True, sort_keys=False)
print(f"生成 {len(all_cases)} 条,待审核 {sum(1 for c in all_cases if c.get('needs_review'))} 条")
我们第一次跑出来 512 条。别急着高兴,里面有一部分是废话,比如给查询接口生成“删除成功”的断言。人工删了 43 条,补了 60 条业务规则,最终 529 条。
AI 在这里的价值不是替你拍板,是把草稿写出来。你改草稿,比从零写快得多。
用 pytest 接住这些用例
我不喜欢把用例写死成 500 个函数,太乱。用 pytest.mark.parametrize 动态加载 YAML,报告里照样一条条显示。
conftest.py:
import os
import pytest
import requests
BASE_URL = os.getenv("API_BASE_URL", "http://test-api.internal")
@pytest.fixture(scope="session")
def api():
s = requests.Session()
s.headers.update({
"X-Env": "regression",
"Authorization": f"Bearer {os.getenv('API_TOKEN')}",
})
yield s
s.close()
tests/test_generated_api.py:
import json
import jsonschema
import pytest
import yaml
from jsonpath_ng import parse
def load_cases():
with open("cases/generated.yaml", encoding="utf-8") as f:
return yaml.safe_load(f)
def check_rule(resp, rule):
if rule["type"] == "status":
assert resp.status_code == rule["eq"], resp.text
elif rule["type"] == "jsonpath":
matches = parse(rule["path"]).find(resp.json())
assert matches, f"jsonpath 没匹配到: {rule['path']}"
assert matches[0].value == rule["eq"], f"{matches[0].value} != {rule['eq']}"
else:
raise AssertionError(f"未知断言类型: {rule['type']}")
@pytest.mark.parametrize("case", load_cases(), ids=lambda c: c["id"])
def test_generated_api(api, case):
url = case["path"]
for k, v in case.get("path_params", {}).items():
url = url.replace("{" + k + "}", str(v))
resp = api.request(
case["method"],
BASE_URL + url,
params=case.get("query"),
json=case.get("body"),
headers=case.get("headers"),
timeout=10,
)
assert resp.status_code in case["expected_status"], resp.text
if case.get("response_schema"):
jsonschema.validate(resp.json(), case["response_schema"])
for rule in case.get("asserts", []):
check_rule(resp, rule)
跑起来:
pytest tests/test_generated_api.py -n 8 --dist loadscope -m regression
-n 8 是 pytest-xdist,8 个进程并行。写接口我单独放在 tests/test_order_write.py,不让生成器碰,因为需要造数据和清理。
断言别只断 200
这是我踩过最大的坑。第一版生成器只断 status_code == 200,结果接口返回 {"code": 500, "msg": "系统异常"} 也绿。后来加了三层:
- HTTP 状态码;
- 响应 schema 用 jsonschema 校验;
- 业务断言,比如 $.code == 0、$.data.id 非空、金额大于 0。
AI 可以帮你生成业务断言候选,但必须人工过一遍。尤其是金额、状态机、权限这些,模型很容易想当然。
数据依赖和清理
500 条用例如果互相污染,跑一次就废。我的做法:
- 查询类用例随便跑;
- 写接口用例单独写,不放进生成器;
- 必须造数据的,用 fixture,yield 后面清理;
- 用 UUID 后缀避免重复;
- 不依赖执行顺序,pytest 默认乱序也能跑。
简单 fixture:
import uuid
@pytest.fixture
def new_order(api):
order_no = f"auto_{uuid.uuid4().hex[:8]}"
resp = api.post(BASE_URL + "/api/order/create", json={"orderNo": order_no})
assert resp.status_code == 200
data = resp.json()["data"]
yield data
api.delete(BASE_URL + f"/api/order/{data['id']}")
并行之后,3 天变 3 小时
现在 CI 里分两档:
- MR:pytest -m smoke -n 4,大概 20 分钟;
- 每晚:pytest -m regression -n 8,全量 529 条,3 小时左右。
为什么不是 30 分钟?因为有些接口本身慢,还有限流和测试数据准备。但对比之前 3 天手工回归,已经不是一个量级。
命令大概这样:
pytest -n 8 \
-m "regression and not slow" \
--alluredir=allure-results \
--maxfail=20
--maxfail=20 是防止一个环境挂了,后面 500 条全红,报告没法看。
几个坑,提前说
- AI 会编字段。提示词里写死“只根据 schema”,仍然可能编。必须人工审核 needs_review。
- Swagger 不准,生成全废。接口文档和实际不一致,AI 只会放大这个错误。
- 写接口别全自动。删除、支付、退款、发货,这些让 AI 生成用例,迟早出事。
- 断言太弱等于没测。只断 200 的用例,不如不写。
- 并行不是万能。有状态接口、限流接口,该串行就串行。
- Token 会过期。回归跑 3 小时,中间 token 失效很常见,需要自动刷新或长有效期测试 token。
最后说点实在的
这套东西最值钱的地方,不是 AI 帮你写了多少代码,而是你把 Swagger 里沉睡的 schema 变成了可执行资产。新增接口时,跑一遍抽取和生成,人工审半小时,补十几条用例,比从零写轻松太多。
但别一上来就全量。先挑 20 个稳定接口试点,把提示词、断言规则、数据清理跑顺,再铺开。Swagger 规范是上限,AI 只是加速器。Swagger 瞎写,AI 只会帮你更快地瞎写。
关于我们
本文部分内容参考了霍格沃兹测试开发学社整理的相关技术资料,主要涉及软件测试、自动化测试、测试开发及 AI 测试等内容,侧重测试实践、工具应用与工程经验整理。
- 点赞
- 收藏
- 关注作者
评论(0)