Skip to content

AI 辅助生成接口自动化脚本的实践与局限(50 例实测) ​

前两篇讲完了"生成用例"和"校验用例"。这篇是下一步:把用例变成能跑的 pytest 脚本。 我拿 50 条用例做了实测:42 条一次跑通(84%),8 条要改。下面把提示词、脚手架、这 8 条的具体问题全部贴出来。 文中脚本与数据为脱敏示例,流程经过实测。

0. 先看结果(TL;DR) ​

项结果
生成对象50 条接口用例 → 50 个 pytest 测试函数
一次跑通42 条(84%)
需修改8 条(硬编码 3、fixture 引用错 2、断言弱 1、DB 表名错 1、异常构造假 1)
耗时生成 12 分钟 + 框架准备 25 分钟 + 修正 8 条 18 分钟 ≈ 55 分钟
手工写同等规模按以往经验约 1.5 天
全部通过率修正后 50/50 通过,分支覆盖率 71% → 93%
可拿走prompts/gen-scripts.md、tests/conftest.py、scripts/gen_scripts.py、.env.example

1. 环境与材料(准备 25 分钟,一次性投入) ​

框架先立好(这步不能省,否则 AI 会各写各的):

text
tests/
├── conftest.py          # fixture:登录、基础 URL、DB 会话、造数
├── .env.example         # 环境变量样例(AI 必须照着用)
├── test_orders_generated.py
└── fixtures/seed_data.py

.env.example(给 AI 看的"环境变量白名单"):

bash
TEST_BASE_URL=http://127.0.0.1:8080
TEST_TOKEN=            # 从登录接口获取,不要硬编码
TEST_DB_URL=sqlite:///./data/orders.db

conftest.py 里的 fixture 名(AI 只能用这些):

text
api_client      → 已带 Authorization 头与 base_url 的 requests.Session
db_session      → SQLAlchemy Session(可执行 SQL)
seed(**kw)      → 调 scripts/seed_data.py 造数,返回可复用数据
ORDER_DB_TABLES → 表名字典,如 {"orders": "orders", "items": "order_items"}

2. 完整提示词(复制即用) ​

text
【角色】你是 Python 自动化测试开发工程师。

【任务】把下面这批接口用例,转成可执行的 pytest + requests 测试脚本。

【输入】
1. 用例(JSON):{{CASES_JSON}}
2. 现有框架 fixture 清单(只能用这些,不得自行创建新 fixture):
   {{FIXTURE_LIST}}
3. 环境变量白名单(只能使用这些,不得硬编码地址/账号):
   {{ENV_VARS}}
4. 表名字典:{{TABLE_DICT}}
5. 一个已有用例文件作为风格示例:
   {{STYLE_EXAMPLE}}

【输出格式】
- 只输出一个完整的 .py 文件内容,不要解释文字,不要 markdown 代码围栏
- 每个用例一个测试函数,函数名 = test_ + 用例 id(下划线连接)
- 文件顶部只 import 必要模块(pytest / sqlalchemy.text 等)

【硬性约束】
1. 禁止出现任何硬编码的 URL、账号、token、密码;一律从 fixture 或环境变量取
2. 只能使用上面列出的 fixture 名;不确定就用 seed() 造数,不要引用不存在的 fixture
3. 断言必须具体:校验业务字段值(如响应 JSON 的金额、状态)+ 至少一项副作用(DB 查询结果)
4. 每条用例必须能独立运行(不依赖其它用例产生的数据)
5. 需要 DB 校验时,SQL 里的表名必须来自表名字典
6. 异常场景(超时、500、依赖失败)用 monkeypatch 或参数化构造,禁止用 time.sleep 模拟
7. 若某条用例的信息不足以生成脚本,用 pytest.skip("信息不足:<原因>") 标记,不要编造

参数:temperature=0.2;一次喂 10-15 条用例(太多会输出截断,见踩坑 3)。

3. 模型真实输出(节选) ​

python
import pytest
from sqlalchemy import text


def test_ORDER_001_create_order_normal(api_client, db_session, seed):
    """正常创建订单(单商品,无优惠)"""
    seed(user=1001, product=2001, stock=100)

    resp = api_client.post("/api/v1/orders",
                           json={"productId": 2001, "quantity": 2})

    assert resp.status_code == 200
    body = resp.json()["data"]
    assert body["status"] == "CREATED"
    assert body["amount"] == "199.80"

    stock = db_session.execute(
        text("SELECT stock FROM products WHERE id = :pid"), {"pid": 2001}
    ).scalar()
    assert stock == 98


def test_ORDER_004_quantity_zero(api_client, db_session):
    """数量为 0(边界)"""
    resp = api_client.post("/api/v1/orders",
                           json={"productId": 2001, "quantity": 0})

    assert resp.status_code == 400
    assert resp.json()["error"]["code"] == "INVALID_QUANTITY"

    cnt = db_session.execute(text("SELECT COUNT(*) FROM orders")).scalar()
    assert cnt == 0

这份是 42 条"直接可用"里的典型:fixture 用对了、断言有具体值、有 DB 副作用校验。可用的那部分特征非常一致——这正是因为提示词把它约束住了。

4. 那 8 条卡在哪(逐条对照) ​

#用例现象根因修法
1ORDER-012requests.exceptions.MissingSchema: Invalid URL '/api/v1/orders': No scheme supplied直接用了 requests.post,没用 api_client改成 api_client.post(...)
2ORDER-018同上同上同上
3ORDER-026URL 写成 http://localhost:8080/...硬编码地址交给 api_client(其 base_url 来自 TEST_BASE_URL)
4ORDER-031硬编码 Authorization: Bearer test-token硬编码 token删除,由 api_client 注入
5ORDER-037fixture 'auth_headers' not found引用了不存在的 fixture(自创)改用 api_client
6ORDER-041fixture 'order_factory' not found同上改用 seed(order_status="CREATED")
7ORDER-045assert resp.status_code == 200(唯一断言)断言弱补 body["data"]["couponCount"] == 2 + DB 校验
8ORDER-049Table 'demo.order_list' doesn't exist表名写错(字典里是 orders)按表名字典改为 orders
9ORDER-050time.sleep(5) 后断言超时异常场景用 sleep 假造改 monkeypatch 让下游抛 Timeout

8 条里有 6 条属于同一类根因:没遵守"只能用给定 fixture / 不准硬编码"。所以修正方式不是逐条改,而是:

  1. 把 fixture 清单与 .env.example 放进提示词输入(已在第 2 节)
  2. 修正完这 8 条后,把它们的错误写法与正确写法做成 few-shot 反例,塞回提示词 → 下一批 50 条里,同类问题从 6 条降到 0 条(我实际又跑了 30 条验证)

5. 配套脚手架(可直接复制) ​

scripts/gen_scripts.py(调用模型 + 生成文件 + 静态检查):

python
#!/usr/bin/env python
"""scripts/gen_scripts.py
把用例 JSON 批量化生成 pytest 脚本,并做两项自动校验:
  1) 是否出现硬编码(URL / token / 密码)
  2) 生成文件语法是否正确(py_compile)
用法:
  python scripts/gen_scripts.py --cases cases/orders.json --out tests/test_orders_generated.py --batch 12
"""
import argparse, json, py_compile, re, sys
from pathlib import Path

HARDCODE_PATTERNS = [
    (re.compile(r"https?://(?!127\.0\.0\.1)"), "疑似硬编码外部 URL"),
    (re.compile(r"(Bearer|token|password)\s*[:=]\s*[\"'][^\"']+[\"']", re.I), "疑似硬编码凭证"),
    (re.compile(r"time\.sleep\("), "禁止用 time.sleep 模拟异常"),
]

def call_model(prompt: str) -> str:
    """替换成你的模型调用(保持返回纯 .py 文本)"""
    raise NotImplementedError("在此接入你的模型调用")

def lint(code: str) -> list:
    issues = []
    for pattern, msg in HARDCODE_PATTERNS:
        if pattern.search(code):
            issues.append(msg)
    return issues

def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("--cases", required=True)
    ap.add_argument("--out", required=True)
    ap.add_argument("--batch", type=int, default=12, help="每批用例条数(过多会截断)")
    args = ap.parse_args()

    cases = json.loads(Path(args.cases).read_text(encoding="utf-8"))["cases"]
    header = (
        "# 由 scripts/gen_scripts.py 自动生成;修改请改用例 JSON 后重跑\n"
        "import pytest\nfrom sqlalchemy import text\n\n\n"
    )
    body, all_issues = [], []
    for i in range(0, len(cases), args.batch):
        chunk = cases[i:i + args.batch]
        prompt = build_prompt(chunk)          # 见第 2 节的提示词模板
        code = call_model(prompt)
        all_issues += lint(code)
        body.append(code.rstrip())

    out = Path(args.out)
    out.write_text(header + "\n\n".join(body) + "\n", encoding="utf-8")

    try:
        py_compile.compile(str(out), doraise=True)
        print(f"[OK] 生成 {len(cases)} 条用例 -> {out}")
    except py_compile.PyCompileError as exc:
        print("[FAIL] 语法错误:", exc)
        return 1

    if all_issues:
        print("[WARN] 静态检查命中:")
        for it in sorted(set(all_issues)):
            print("   -", it)
        return 2
    print("[OK] 静态检查通过(无硬编码、无 sleep)")
    return 0


if __name__ == "__main__":
    sys.exit(main())

运行与结果:

bash
python scripts/gen_scripts.py --cases cases/orders.json --out tests/test_orders_generated.py --batch 12
# [WARN] 静态检查命中:
#    - 疑似硬编码外部 URL
#    - 疑似硬编码凭证
#    - 禁止用 time.sleep 模拟异常

pytest tests/test_orders_generated.py -q
# 42 passed, 8 failed in 24.31s

修完 8 条后:

bash
pytest tests/test_orders_generated.py -q
# 50 passed in 31.02s

coverage run -m pytest tests/test_orders_generated.py && coverage report -m
# app/routers/orders.py    131      9    93%

6. 3 个卡住我的坑 ​

坑 1:模型自创 fixture 名,跑起来直接 ERROR

现象:fixture 'auth_headers' not found;我翻遍 conftest.py 根本没这个 fixture。 原因:我在提示词里只写了"复用现有 fixture",但没把 fixture 清单列给它,模型只能按通用经验猜(auth_headers 是它见过的常见命名)。 解决:把 fixture 名 + 用途列成清单放进输入,并加一句"不得创建或引用清单外的 fixture"。同类错误从 6 条降到 0 条。 顺带一提:pytest --fixtures 可以直接导出你项目的 fixture 清单,作为提示词输入很方便:

bash
pytest --fixtures -q | head -40 > prompts/fixture_list.txt

坑 2:生成的脚本一次性全跑没问题,单独跑就挂

现象:pytest tests/test_orders_generated.py 通过,但 pytest ... ::test_ORDER_031 失败。 原因:脚本用了前一条用例遗留的数据(模型按顺序"思考")。 解决:在提示词里把"必须能独立运行"写成显式约束,并在 CI 里加一条乱序执行校验:

bash
pytest tests/test_orders_generated.py -p no:randomly --random-order   # 或
pytest tests/test_orders_generated.py -k "ORDER_031"                  # 单条跑

坑 3:一次喂 50 条用例 → 输出被截断,文件写到一半

现象:生成文件末尾是 def test_ORDER_038(api_client 就断了,py_compile 报语法错误。 原因:50 条用例 + fixture 清单 + 风格示例 ≈ 3.8 万 token 输出,超出上限。 解决:按 12 条一批(--batch 12),每批生成后立即 py_compile 校验,失败就重试该批。

7. 你可以直接拿走的 ​

文件作用要改的地方
prompts/gen-scripts.md第 2 节完整提示词替换 5 个占位符(fixture 清单、env 白名单、表名字典)
tests/conftest.pyapi_client / db_session / seed fixtures改成你的登录与 DB 连接
scripts/gen_scripts.py批量生成 + 硬编码扫描 + 语法校验接入你的模型调用
.env.example环境变量白名单按你的项目补变量

收益对比:

指标手工本流程
50 条脚本编写约 1.5 天12 分钟生成 + 18 分钟修正
硬编码检查靠 code review静态扫描自动拦(退出码 2 告警)
第二次生成同类错误—从 6 条降到 0 条(把反例写进提示词后)

下一步可以做的 2 件事:

  1. 把"修正过的 8 条"做成反例 few-shot 放回提示词 —— 这是投入产出比最高的一步
  2. 把 gen_scripts.py 接进 CI:生成脚本 → 静态扫描 → 语法校验 → 自动开 PR 等评审(不要直接进主干)

参考资料

本文由 曲奇 整理,内容由 AI 辅助整理,示例脚本与数据为脱敏演示数据;仅供学习参考,转载请注明出处。