【让 AI 生成的测试用例变成 pytest API 自动化脚本】

系列方向: 从大语言模型基础调用开始,逐步拆解 Prompt、RAG、规则校验、用例质量评估和自动化执行,最终整合成一个可以运行、可以验证的 AI Test Demo。

本篇重点: 将结构化测试用例转化为 pytest API 自动化测试数据,完成 HTTP 请求、参数映射和结果断言。重点不是让 AI 随意生成 Python 代码,而是让 AI 输出的数据进入一套可控的自动化执行框架。

一、测试用例生成出来了,为什么还不能直接执行?

先回顾一下前面的成果。

我们已经有这样一条流程:

text 复制代码
需求文档
   |
   v
RAG 检索业务规则
   |
   v
LLM 生成测试用例
   |
   v
规则校验
   |
   v
质量评估
   |
   v
生成结构化 JSON

例如,AI 生成了一条创建订单的测试用例:

json 复制代码
{
  "id": "TC001",
  "title": "未登录用户创建订单",
  "rule_ids": ["R001"],
  "steps": [
    "提交创建订单请求"
  ],
  "expected_result": "接口拒绝创建订单",
  "expected_behavior": "defined"
}

这条数据可以阅读,也可以保存,但它还不能直接被 pytest 执行。

原因很简单:

steps 只是自然语言描述,pytest 并不知道应该请求哪个接口、传什么参数、使用什么请求头,也不知道应该检查 HTTP 状态码还是业务错误码。

如果直接让大模型生成完整 Python 自动化脚本,会出现另外一类问题:

  • 不同用例生成的代码结构不一致。
  • 请求地址和参数可能被模型编造。
  • 断言逻辑可能不符合真实接口契约。
  • 模型可能生成不安全的代码。
  • 后续想统一维护请求逻辑会比较困难。

所以这次我选择另一种方式:

让 AI 生成结构化测试数据,自动化框架负责执行。

这样模型负责描述测试意图,pytest 负责真正执行测试。

二、先定义一份能够执行的测试用例结构

我们需要对前面使用的 JSON 格式做一次扩展。

之前的用例主要用于表达业务测试意图,现在需要补充接口执行信息。

例如:

json 复制代码
{
  "id": "TC001",
  "title": "未登录用户创建订单",
  "rule_ids": ["R001"],
  "request": {
    "method": "POST",
    "path": "/api/orders",
    "headers": {},
    "body": {
      "product_id": 1001,
      "quantity": 1
    }
  },
  "expected": {
    "status_code": 401,
    "json": {
      "code": "UNAUTHORIZED"
    }
  }
}

这里需要特别说明:

/api/orders、HTTP 401 和 UNAUTHORIZED 都只是本 Demo 的接口契约示例,不代表任何真实项目的实际定义。

真实项目中,接口路径、认证机制、业务错误码都应该来自 API 文档或经过确认的接口契约。

为了让后面的自动化执行更清晰,我们把用例拆成三个部分:

部分 内容 作用
测试信息 id、title、rule_ids 记录测试目的
请求信息 method、path、headers、body 描述如何调用接口
预期信息 status_code、json 描述如何判断结果

这样一条用例就不再只是给人看的文字,而是可以被程序读取的测试数据。

三、搭建 pytest 自动化执行模块

在前面的项目基础上增加几个目录:

text 复制代码
llm-testing-demo/
│
├── automation/
│   ├── __init__.py
│   ├── api_client.py
│   ├── test_runner.py
│   └── test_generated_cases.py
│
├── examples/
│   └── executable_cases.json
│
├── outputs/
│
├── pytest.ini
└── requirements.txt

这几个文件分别负责:

  • api_client.py:统一封装 HTTP 请求。
  • test_runner.py:读取用例并执行请求和断言。
  • test_generated_cases.py:pytest 测试入口。
  • executable_cases.json:存放已经审核通过的可执行用例。

为什么要单独增加一个 executable_cases.json?

因为前面模型生成的候选用例,不一定都能执行。

例如,某条用例的预期结果还依赖未确认的幂等策略,那么它就不应该直接进入自动化执行阶段。

我们应该把候选用例和审核通过的可执行用例区分开。

四、第一步:准备接口测试数据

创建 examples/executable_cases.json:

json 复制代码
[
  {
    "id": "TC001",
    "title": "未登录用户创建订单",
    "request": {
      "method": "POST",
      "path": "/api/orders",
      "headers": {},
      "body": {
        "product_id": 1001,
        "quantity": 1
      }
    },
    "expected": {
      "status_code": 401,
      "json": {
        "code": "UNAUTHORIZED"
      }
    }
  },
  {
    "id": "TC002",
    "title": "购买数量为0时创建订单",
    "request": {
      "method": "POST",
      "path": "/api/orders",
      "headers": {
        "Authorization": "Bearer test-token"
      },
      "body": {
        "product_id": 1001,
        "quantity": 0
      }
    },
    "expected": {
      "status_code": 400,
      "json": {
        "code": "INVALID_QUANTITY"
      }
    }
  }
]

这里设计了两条测试用例:

  • TC001:验证未登录用户不能创建订单。
  • TC002:验证购买数量为 0 时不能创建订单。

注意,第二条用例使用了测试环境中的示例 Token。真实项目中应该通过认证 Fixture 获取测试凭证,而不是把真实 Token 写进文件。

另外,接口响应中的 code 字段也是示例约定,实际项目需要根据接口返回结构调整。

五、第二步:封装统一的 API Client

如果每条测试用例都自己调用 requests.post(),后续维护会比较麻烦。

例如,接口超时、请求日志、基础地址变化、公共请求头等,都可能需要修改很多地方。

所以先统一封装一个客户端。

安装依赖:

bash 复制代码
pip install pytest requests

创建 automation/api_client.py:

python 复制代码
import requests


class APIClient:

    def __init__(self, base_url, timeout=10):
        self.base_url = base_url.rstrip("/")
        self.timeout = timeout

    def request(
        self,
        method,
        path,
        headers=None,
        body=None
    ):
        url = self.base_url + path

        response = requests.request(
            method=method,
            url=url,
            headers=headers or {},
            json=body,
            timeout=self.timeout
        )

        return response

这里没有把所有请求方法分别写成 get()、post()、put()。

因为当前 Demo 只需要一个统一请求入口,后续如果需要增加认证、重试或日志记录,可以在这个类中集中处理。

但要注意:生产环境不应该对所有请求盲目重试。特别是创建订单这种可能产生副作用的 POST 请求,重试必须考虑幂等性。

六、第三步:编写测试用例执行器

接下来需要解决一个问题:

JSON 中的测试数据,如何映射成真正的 HTTP 请求和断言?

创建 automation/test_runner.py:

python 复制代码
def execute_case(client, case):
    request_data = case["request"]
    expected = case["expected"]

    response = client.request(
        method=request_data["method"],
        path=request_data["path"],
        headers=request_data.get("headers"),
        body=request_data.get("body")
    )

    # 检查 HTTP 状态码
    assert response.status_code == expected["status_code"], (
        f"{case['id']} 状态码不符合预期,"
        f"实际:{response.status_code},"
        f"预期:{expected['status_code']},"
        f"响应:{response.text}"
    )

    # 检查 JSON 业务字段
    actual_json = response.json()

    for key, expected_value in expected.get("json", {}).items():
        assert actual_json.get(key) == expected_value, (
            f"{case['id']} 字段 {key} 不符合预期,"
            f"实际:{actual_json.get(key)},"
            f"预期:{expected_value}"
        )

这里的执行逻辑非常直接:

  1. 从用例中获取请求方法。
  2. 获取接口路径。
  3. 读取请求头和请求体。
  4. 发送 HTTP 请求。
  5. 检查 HTTP 状态码。
  6. 检查 JSON 中指定的业务字段。

这里还有一个值得注意的地方:

我们没有让 AI 生成断言代码,而是让它输出预期数据,由固定的执行器完成断言。

这样做的好处是,测试框架的执行逻辑保持一致,不会因为模型生成方式不同而发生变化。

当前实现只支持简单的 JSON 顶层字段比较。后续如果需要检查嵌套字段、数组、数据库状态或响应时间,可以逐步扩展断言机制。

七、第四步:接入 pytest

现在已经有了 API Client 和执行器,接下来只需要让 pytest 读取测试用例。

创建 automation/test_generated_cases.py:

python 复制代码
import json
import os
from pathlib import Path

import pytest

from automation.api_client import APIClient
from automation.test_runner import execute_case


CASE_FILE = Path("examples/executable_cases.json")


def load_cases():
    with CASE_FILE.open("r", encoding="utf-8") as file:
        return json.load(file)


@pytest.fixture(scope="session")
def api_client():
    base_url = os.getenv("TEST_BASE_URL")

    if not base_url:
        pytest.fail("未配置 TEST_BASE_URL")

    return APIClient(base_url=base_url)


def load_case_ids():
    return [
        case["id"]
        for case in load_cases()
    ]


@pytest.mark.parametrize(
    "case",
    load_cases(),
    ids=load_case_ids()
)
def test_generated_case(api_client, case):
    execute_case(api_client, case)

这里使用了 pytest 的 parametrize。

它的作用是将 JSON 中的多条测试用例展开成独立的测试项。

例如,原本只有一个测试函数:

python 复制代码
def test_generated_case():
    pass

经过参数化之后,pytest 可以分别执行 TC001 和 TC002。

这样有几个好处:

  • 每条用例有独立执行结果。
  • 失败时可以定位具体用例。
  • 可以使用 pytest 的测试报告能力。
  • 后续可以把 AI 生成的用例数量动态扩展。

这里有一个工程细节:parametrize 在测试收集阶段就会读取 JSON,因此文件不存在或格式错误时,pytest 可能在测试执行前就失败。实际项目中应该提前校验用例文件,并给出更清晰的错误提示。

八、第五步:运行自动化测试

创建 pytest.ini:

ini 复制代码
[pytest]
testpaths = automation
python_files = test_*.py
addopts = -v

然后配置测试环境地址:

bash 复制代码
export TEST_BASE_URL="http://127.0.0.1:8000"

Windows PowerShell:

powershell 复制代码
$env:TEST_BASE_URL="http://127.0.0.1:8000"

运行:

bash 复制代码
pytest

如果接口服务已经启动,并且接口契约与示例用例一致,pytest 就会分别执行两条测试。

预期测试项:

text 复制代码
TC001 未登录用户创建订单
TC002 购买数量为0时创建订单

这只是预期的测试项名称,不是实际运行结果。

如果没有真实订单服务,这两条用例不会凭空通过。我们需要先准备一个符合接口契约的测试环境,或者编写 Mock 服务验证执行器本身。

九、没有真实接口,怎么先验证执行器?

我认为这里很值得做一个小实验。

因为在实际开发中,我们经常会遇到这样的情况:

自动化框架已经写好了,但后端接口还没有部署完成。

这时候可以使用 Mock 服务验证请求构造和断言逻辑。

例如,创建一个简单的 Flask 服务。

安装:

bash 复制代码
pip install flask

创建 mock_server.py:

python 复制代码
from flask import Flask, request, jsonify

app = Flask(__name__)


@app.route("/api/orders", methods=["POST"])
def create_order():
    if not request.headers.get("Authorization"):
        return jsonify({
            "code": "UNAUTHORIZED"
        }), 401

    data = request.get_json(silent=True) or {}

    quantity = data.get("quantity")

    if (
        not isinstance(quantity, int)
        or isinstance(quantity, bool)
        or quantity <= 0
    ):
        return jsonify({
            "code": "INVALID_QUANTITY"
        }), 400

    return jsonify({
        "code": "SUCCESS",
        "order_id": "MOCK-ORDER-001"
    }), 201


if __name__ == "__main__":
    app.run(
        host="127.0.0.1",
        port=8000,
        debug=False
    )

启动:

bash 复制代码
python mock_server.py

这只是一个用于验证执行器的 Mock 接口。

它没有实现真实订单创建、库存扣减、数据库事务和幂等策略。

现在运行:

bash 复制代码
pytest

我们可以验证:

  • 未登录请求是否得到 401。
  • 数量为 0 的请求是否得到 400。
  • 执行器能否正确解析响应。
  • pytest 能否识别每条用例的执行结果。

这里的价值不是证明订单业务正确,而是先验证 AI 输出的数据能否被自动化框架正确消费。

这是我比较推荐的开发顺序:先验证工具链,再接入真实业务系统。

十、AI 生成的用例,不能直接进入执行环境

到这里,技术链路已经比较完整了。

但我认为有一个问题必须单独强调:

模型生成的测试用例,不应该未经检查就直接执行。

特别是当测试工具开始接入真实业务环境时,风险会明显增加。

例如:

  • 模型生成了一个删除数据的请求。
  • 模型把测试地址错误地指向生产环境。
  • 模型生成了超出预期范围的请求参数。
  • 模型错误地使用了管理员身份。
  • 模型生成的预期结果本身就是错误的。

因此,正式执行前至少要增加以下控制:

  1. 只允许访问配置好的测试环境地址。
  2. 对 HTTP 方法和接口路径设置白名单。
  3. 对请求参数进行 Schema 校验。
  4. 对破坏性操作设置人工审批。
  5. 对 Token、密码等敏感信息进行保护。
  6. 对超时、并发和请求数量设置限制。
  7. 不允许模型输出任意 Python 代码并直接执行。

尤其是最后一点。

我们现在采用的是 JSON 数据驱动模式,模型只负责输出受约束的数据,程序负责解释和执行。

这比直接运行模型生成的 Python 脚本更容易审计,也更容易控制风险。

十一、这一阶段还有哪些不足?

目前的执行框架已经能完成简单的 API 请求和断言,但离真正的企业级自动化平台还有距离。

当前能力 后续需要补充
JSON 参数化执行 更完整的用例 Schema
HTTP 状态码断言 嵌套 JSON、数据库和业务状态断言
简单 Mock 服务 真实测试环境接入
pytest 执行 测试报告和失败日志分析
固定测试 Token 统一认证与凭证管理
单请求执行 数据清理、依赖管理和并发控制

还有一个容易忽略的问题:测试数据之间可能存在依赖关系。

例如,创建订单成功后,需要查询订单详情;查询订单详情又依赖前一步生成的订单 ID。

这种场景不能简单地把每条 JSON 用例独立执行,需要增加测试数据关联、前置条件和后置清理机制。

这部分可以放在后续工程化阶段,不必在第一版 Demo 中一次性完成。

十二、从生成到执行,AI Test Demo 又向前走了一步

回顾整个流程:

text 复制代码
需求输入
   |
   v
RAG 检索业务规则
   |
   v
LLM 生成候选用例
   |
   v
规则校验
   |
   v
质量评估
   |
   v
人工确认可执行用例
   |
   v
JSON 数据驱动
   |
   v
pytest 执行
   |
   v
API 请求与断言
   |
   v
测试执行结果

这次最大的变化,是我们不再停留在"AI 能不能生成测试用例"这个问题上,而是开始验证:

  • 生成的数据能不能被自动化框架消费?
  • 测试请求能不能真正发出去?
  • 预期结果能不能被程序判断?
  • 执行失败能不能定位到具体用例?

这些问题比单纯比较模型生成了多少条用例更接近实际测试工作。

本篇小结: AI 生成测试用例的价值,不在于让它替代测试工程师写 Python,而在于把需求理解、场景设计和自动化执行连接起来。模型生成候选数据,框架执行确定性逻辑,人工把控业务正确性,这样的分工才更容易形成稳定的工程流程。

相关推荐
天天代码码天天1 小时前
我开源了一个家庭备份工具:把手机照片和视频,存回自己的电脑
人工智能
AI源码1 小时前
基于机器学习的古籍文字识别与断句校正系统
人工智能·机器学习
用户8339049606951 小时前
记账大师 AIUI购物记账与交互可靠性实践
人工智能
数智人生局1 小时前
配置驱动的通用 ETL 执行引擎:告别"每个需求写一个 Spark 作业"(改配置不发版)
人工智能
Neurocoda1 小时前
基于 NumPy 从零实现 Tiny-AlexNet 在 MNIST 数据集上的验证
人工智能·深度学习
youcans_1 小时前
【嵌入式软件AI编程】(3)面向 AI 协同开发的 STM32 硬件信息标准化
人工智能·stm32·嵌入式硬件·ai编程
AOI小白新手上路1 小时前
深度学习算力购置方案分析:本地 RTX 4090 服务器 vs 云端租赁
服务器·人工智能·深度学习
QXWZ_IA1 小时前
北斗高精度定位在泛自然资源行业的技术演进与应用方向
大数据·人工智能
坐吃山猪2 小时前
果蝇AI06-Mac部署实操
人工智能·深度学习·机器学习·果蝇ai