TL;DR

系列方向: 从零搭建 AI Test Agent,逐步实现大模型接入、工具调用、任务规划、异常恢复、HTTP 接口接入和自动化测试执行。

本篇重点: 在第 07 篇接入 HTTP 测试环境的基础上,让 Agent 根据业务需求自动提取测试点、生成结构化测试用例,并通过已有接口工具执行测试。

一、为什么要让 AI Agent 自动生成测试用例?

在实际测试工作中,编写测试用例并不是简单地列出几个输入和预期结果。

拿订单系统举例,产品需求可能只有这样一段描述:

订单创建成功后,订单金额必须大于 0。
订单状态只能是 pending、paid、cancelled。
用户查询订单时,如果订单不存在,应返回 404。
订单查询接口需要返回订单编号、金额和状态。

测试人员看到这些需求后,需要进一步分析:

  • 正常订单应该如何验证?
  • 金额为 0 怎么处理?
  • 负数金额是否允许?
  • 订单状态不合法怎么办?
  • 查询不存在的订单应该返回什么?
  • 接口响应字段缺失怎么办?

这些工作通常需要测试人员先分析需求,再编写测试用例,最后手动或通过自动化脚本执行。

如果把这套工作交给 AI Agent,可以形成一个新的执行流程:

否

是

输入业务需求

LLM 分析需求

提取业务规则

生成测试点

生成结构化测试用例

Python 校验用例

用例是否合法?

拒绝执行或要求修正

调用 HTTP 测试工具

执行测试断言

汇总测试结果

这里需要明确一点:

AI 负责分析和生成测试用例,Python 负责校验和执行测试用例。

不能因为大模型生成了一段测试步骤,就直接允许它执行任意 Python 代码或者访问任意接口。

这也是本篇实现时需要坚持的设计原则。

二、本篇准备实现什么?

本篇继续使用第 07 篇的订单 API,不重新搭建 HTTP 服务。

已有接口:

GET /api/orders/{order_id}

已有工具:

query_order_detail
check_order_amount
check_order_status

本篇增加三个能力:

模块作用
需求分析从自然语言需求中提取业务规则
用例生成生成结构化测试用例
用例执行校验用例并调用 HTTP 工具执行

最终希望实现这样的效果:

输入:

请根据订单业务规则生成测试用例并执行。

1. 订单金额必须大于 0。
2. 订单状态只能是 pending、paid、cancelled。
3. 查询不存在的订单时,应返回 HTTP 404。

Agent 自动生成测试用例,然后调用 HTTP 接口执行。

三、先定义测试用例的数据结构

在真正调用大模型之前,先确定测试用例应该长什么样。

这是一个容易被忽略的细节。

如果不提前定义数据结构,大模型可能生成各种不同格式的结果:

测试场景:正常订单
测试数据:ORDER1001
预期结果:金额正确,状态正确

或者:

{
  "name": "正常订单测试",
  "order": "ORDER1001"
}

对于人来说,这些内容都能理解,但程序不一定能够稳定处理。

因此,我们先定义一个统一的 JSON 格式。

{
  "case_id": "TC001",
  "title": "正常订单查询",
  "order_id": "ORDER1001",
  "expected_http_status": 200,
  "expected_business_valid": true,
  "checks": [
    "amount_positive",
    "status_valid"
  ]
}

字段说明:

字段含义
case_id测试用例编号
title测试用例名称
order_id测试订单编号
expected_http_status预期 HTTP 状态码
expected_business_valid预期业务校验结果
checks需要执行的检查项

这里没有让模型生成 Python 代码,也没有让模型直接生成 HTTP 请求。

它只负责生成结构化测试数据。

这样后续无论接入什么模型,执行层都可以保持稳定。

四、实现测试用例生成模块

新建 case_generator.py。

这个模块主要负责调用大模型,将自然语言需求转换成 JSON 测试用例。

import json
import os

from urllib.request import Request, urlopen


LLM_API_KEY = os.getenv("LLM_API_KEY")
LLM_BASE_URL = os.getenv("LLM_BASE_URL", "").rstrip("/")
LLM_MODEL = os.getenv("LLM_MODEL")


SYSTEM_PROMPT = """
你是一名资深软件测试工程师。

请根据用户提供的业务需求生成接口测试用例。

要求:
1. 只生成 JSON,不要输出 Markdown。
2. 不允许生成 Python 代码或可执行脚本。
3. 每个测试用例必须包含:
   case_id、title、order_id、
   expected_http_status、
   expected_business_valid、checks。
4. expected_http_status 只能是合理的 HTTP 状态码。
5. checks 只能从以下范围选择:
   amount_positive、status_valid。
6. 测试用例应覆盖正常场景和异常场景。
7. 不要编造实际接口已经执行的结果。

输出格式:

{
  "cases": [
    {
      "case_id": "TC001",
      "title": "正常订单查询",
      "order_id": "ORDER1001",
      "expected_http_status": 200,
      "expected_business_valid": true,
      "checks": [
        "amount_positive",
        "status_valid"
      ]
    }
  ]
}
"""


def generate_cases(requirement):

    if not LLM_API_KEY or not LLM_BASE_URL or not LLM_MODEL:
        raise RuntimeError("请先配置模型服务环境变量")

    payload = {
        "model": LLM_MODEL,
        "messages": [
            {
                "role": "system",
                "content": SYSTEM_PROMPT
            },
            {
                "role": "user",
                "content": requirement
            }
        ],
        "temperature": 0
    }

    request = Request(
        f"{LLM_BASE_URL}/chat/completions",
        data=json.dumps(payload).encode("utf-8"),
        headers={
            "Content-Type": "application/json",
            "Authorization": f"Bearer {LLM_API_KEY}"
        },
        method="POST"
    )

    with urlopen(request, timeout=60) as response:
        result = json.loads(
            response.read().decode("utf-8")
        )

    content = result["choices"][0]["message"]["content"]

    return json.loads(content)

这里有一个需要注意的地方:

不同模型服务商对 JSON 输出模式的支持程度不一样。

如果模型经常在 JSON 前后添加解释文字,可以优先使用服务商支持的结构化输出或 JSON Mode。不要简单地通过字符串截取来假装完成了可靠解析。

另外,生成的 JSON 仍然是不可信输入,必须经过下一步校验。

五、增加测试用例校验模块

大模型生成的测试用例不一定正确。

例如,它可能生成:

{
  "case_id": "TC001",
  "title": "正常订单测试",
  "order_id": "ORDER1001",
  "expected_http_status": 999,
  "expected_business_valid": true,
  "checks": [
    "execute_shell"
  ]
}

如果直接执行,就会产生问题。

因此,我们需要增加一个用例校验模块。

新建 case_validator.py:

ALLOWED_CHECKS = {
    "amount_positive",
    "status_valid"
}

ALLOWED_HTTP_STATUS = {
    200,
    400,
    404,
    500
}


def validate_cases(data):

    if not isinstance(data, dict):
        raise ValueError("用例数据必须是 JSON 对象")

    cases = data.get("cases")

    if not isinstance(cases, list) or not cases:
        raise ValueError("cases 必须是非空数组")

    case_ids = set()

    for case in cases:

        if not isinstance(case, dict):
            raise ValueError("每条用例必须是对象")

        required_fields = {
            "case_id",
            "title",
            "order_id",
            "expected_http_status",
            "expected_business_valid",
            "checks"
        }

        if not required_fields.issubset(case):
            raise ValueError(
                f"用例缺少必要字段: {case}"
            )

        case_id = case["case_id"]

        if not isinstance(case_id, str) or not case_id.strip():
            raise ValueError("case_id 必须是非空字符串")

        if case_id in case_ids:
            raise ValueError(f"用例编号重复: {case_id}")

        case_ids.add(case_id)

        if not isinstance(case["title"], str):
            raise ValueError("title 必须是字符串")

        if not isinstance(case["order_id"], str):
            raise ValueError("order_id 必须是字符串")

        if case["expected_http_status"] not in ALLOWED_HTTP_STATUS:
            raise ValueError("不支持的预期 HTTP 状态码")

        if not isinstance(case["expected_business_valid"], bool):
            raise ValueError("expected_business_valid 必须是布尔值")

        checks = case["checks"]

        if not isinstance(checks, list):
            raise ValueError("checks 必须是数组")

        if not set(checks).issubset(ALLOWED_CHECKS):
            raise ValueError("包含不允许的检查项")

    return True

这一步的意义不只是检查 JSON 格式。

它还建立了 Agent 和执行程序之间的边界:

  • 模型可以提出测试场景。
  • 模型可以填写测试数据。
  • 模型不能增加任意工具。
  • 模型不能修改程序的执行权限。

实际工程中,还应增加字段长度限制、用例数量限制、订单编号格式校验和重复场景检查。

六、实现测试用例执行模块

接下来,让生成的测试用例真正执行起来。

这里直接复用第 07 篇的 query_order_detail()。

不过,本篇需要对 HTTP 查询结果进行更严格的处理。

新建 case_runner.py:

from http_tools import query_order_detail


ALLOWED_STATUSES = {
    "pending",
    "paid",
    "cancelled"
}


def validate_order(order, checks):

    results = []

    for check in checks:

        if check == "amount_positive":
            amount = order.get("amount")

            passed = (
                isinstance(amount, (int, float))
                and not isinstance(amount, bool)
                and amount > 0
            )

            results.append({
                "check": check,
                "passed": passed,
                "actual": amount,
                "message": "订单金额必须大于 0"
            })

        elif check == "status_valid":
            status = order.get("status")
            passed = status in ALLOWED_STATUSES

            results.append({
                "check": check,
                "passed": passed,
                "actual": status,
                "message": "订单状态必须属于允许范围"
            })

    return results


def run_case(case):

    result = {
        "case_id": case["case_id"],
        "title": case["title"],
        "passed": False,
        "http_status": None,
        "checks": [],
        "error": None
    }

    response = query_order_detail(case["order_id"])

    if not response["success"]:

        result["http_status"] = response.get("http_status")
        result["error"] = response.get("error")

        # 请求失败时,只能根据实际 HTTP 结果判断预期是否满足。
        result["passed"] = (
            result["http_status"]
            == case["expected_http_status"]
        )

        return result

    actual_status = response["http_status"]
    result["http_status"] = actual_status

    if actual_status != case["expected_http_status"]:
        result["error"] = (
            f"HTTP 状态码不符合预期:"
            f"实际 {actual_status},"
            f"预期 {case['expected_http_status']}"
        )
        return result

    order = response.get("data")

    if not isinstance(order, dict):
        result["error"] = "接口没有返回有效订单数据"
        return result

    checks = validate_order(
        order,
        case["checks"]
    )

    result["checks"] = checks

    actual_business_valid = all(
        item["passed"] for item in checks
    )

    result["passed"] = (
        actual_business_valid
        == case["expected_business_valid"]
    )

    return result


def run_cases(cases):

    results = []

    for case in cases:
        results.append(run_case(case))

    return results

这里做了一个重要调整:

测试用例是否通过,不能简单等同于业务断言是否通过。

例如,负数金额测试用例的预期就是发现异常数据。

如果接口返回了负数金额,并且程序正确识别出金额不合法,那么这个测试用例应该判定为通过。

因此这里使用:

result["passed"] = (
    actual_business_valid
    == case["expected_business_valid"]
)

也就是说:

实际业务校验预期业务校验测试用例结果
合法合法通过
不合法不合法通过
合法不合法失败
不合法合法失败

这才是测试用例真正需要表达的含义。

七、整合项目入口

现在将需求分析、用例生成、用例校验和执行整合起来。

修改 main.py:

import json

from case_generator import generate_cases
from case_validator import validate_cases
from case_runner import run_cases


def main():

    requirement = """
    订单业务规则:

    1. 订单金额必须大于 0。
    2. 订单状态只能是 pending、paid、cancelled。
    3. 查询不存在的订单时,应返回 HTTP 404。
    4. 正常订单查询接口应返回 HTTP 200。
    """

    print("========== 1. 生成测试用例 ==========")

    generated = generate_cases(requirement)

    print(json.dumps(
        generated,
        ensure_ascii=False,
        indent=2
    ))

    print("\n========== 2. 校验测试用例 ==========")

    validate_cases(generated)

    print("测试用例校验通过")

    print("\n========== 3. 执行测试用例 ==========")

    results = run_cases(generated["cases"])

    print(json.dumps(
        results,
        ensure_ascii=False,
        indent=2
    ))

    print("\n========== 4. 测试统计 ==========")

    total = len(results)

    passed = sum(
        1 for item in results
        if item["passed"]
    )

    failed = total - passed

    print(f"总用例数:{total}")
    print(f"通过数量:{passed}")
    print(f"失败数量:{failed}")


if __name__ == "__main__":
    main()

项目结构现在变成:

ai-test-agent/
├── mock_server.py
├── http_tools.py
├── agent.py
├── case_generator.py
├── case_validator.py
├── case_runner.py
├── main.py
└── requirements.txt

其中:

  • mock_server.py:提供本地订单 API。
  • http_tools.py:封装 HTTP 请求。
  • agent.py:保留前面文章的 Tool Calling Agent。
  • case_generator.py:根据需求生成测试用例。
  • case_validator.py:检查生成结果是否合法。
  • case_runner.py:执行测试用例。
  • main.py:串联完整流程。

本篇入口采用独立的用例执行器,便于把生成和执行过程拆开验证。下一步再将它接入第 05、06 篇的任务管理和状态持久化模块。

八、测试一下:生成的用例应该是什么样?

启动第 07 篇的 HTTP 服务:

python mock_server.py

然后配置模型服务:

export LLM_API_KEY="你的API_KEY"
export LLM_BASE_URL="你的模型服务地址/v1"
export LLM_MODEL="你的模型名称"

运行:

python main.py

模型可能生成类似下面的用例:

{
  "cases": [
    {
      "case_id": "TC001",
      "title": "正常订单查询",
      "order_id": "ORDER1001",
      "expected_http_status": 200,
      "expected_business_valid": true,
      "checks": [
        "amount_positive",
        "status_valid"
      ]
    },
    {
      "case_id": "TC002",
      "title": "负数金额订单校验",
      "order_id": "ORDER1002",
      "expected_http_status": 200,
      "expected_business_valid": false,
      "checks": [
        "amount_positive",
        "status_valid"
      ]
    },
    {
      "case_id": "TC003",
      "title": "不存在订单查询",
      "order_id": "NOT_FOUND",
      "expected_http_status": 404,
      "expected_business_valid": false,
      "checks": []
    }
  ]
}

这只是符合当前测试数据和业务规则的预期示例,实际生成结果会受到模型理解和生成质量影响,不能保证每次都完全一致。

如果执行结果与预期一致,可能得到:

========== 4. 测试统计 ==========

总用例数:3
通过数量:3
失败数量:0

同样,这里是示例输出,不代表已经实际运行模型和接口得到的结果。

九、这套方案还有哪些不足?

到这里,已经实现了一个基础的 AI 测试用例生成和执行流程。

但如果真的放进企业测试项目,我认为还有几个问题必须解决。

1. AI 生成的用例不一定覆盖完整

模型可能只生成正常场景和少量异常场景,却遗漏边界值、组合条件和权限场景。

因此不能简单地把生成数量当成覆盖质量。

后续可以增加需求条款与测试用例之间的映射关系,检查每条需求是否都有对应测试用例。

2. 预期结果不能完全交给 AI 决定

这是自动生成测试用例时最需要警惕的问题。

如果业务需求写得不清楚,模型可能自行推测某个异常应该返回 400 还是 422。

这种情况下,即使生成的用例结构正确,也不代表预期结果正确。

更合理的方式是:

  • 让 AI 提取业务规则。
  • 对关键规则进行人工确认。
  • 将确认后的规则保存为测试规范。
  • 再根据规范生成和执行测试用例。

3. 当前执行器只支持少量检查项

本篇只有:

amount_positive
status_valid

真实项目中还需要支持:

  • 必填字段检查。
  • 字段类型检查。
  • 字符串长度检查。
  • 日期格式检查。
  • 金额精度检查。
  • 响应结构检查。
  • 业务错误码检查。

这些规则应该由程序实现,而不是让模型临时生成判断代码。

4. 测试数据需要单独管理

本篇直接使用固定订单编号:

ORDER1001
ORDER1002
NOT_FOUND

实际项目中,测试数据可能需要动态创建、清理和隔离。

如果多个测试任务同时执行,还需要避免数据互相影响。

5. 还没有形成完整的测试追踪体系

现在已经能生成用例并执行,但还缺少:

  • 每个需求对应哪些测试用例。
  • 每个用例执行了几次。
  • 每个用例调用了哪些接口。
  • 失败时的请求和响应信息。
  • 测试报告和历史执行记录。

这些内容会在下一篇继续完善。

十、本篇总结

本篇在第 07 篇 HTTP 接口接入的基础上,完成了 AI 自动生成测试用例的基本流程。

实现模块完成内容
需求分析将自然语言业务需求交给模型
用例生成输出结构化 JSON 测试用例
用例校验检查字段、编号、状态码和检查项
接口执行复用 HTTP 查询工具
业务断言检查订单金额和状态
结果统计统计用例通过和失败数量

从工程角度看,这篇最重要的不是让 AI 生成了几条测试用例,而是把原本由测试人员手工完成的几个步骤连接起来:

需求输入 → 测试点分析 → 用例生成 → 用例校验 → 接口执行 → 测试结果。

当然,AI 生成的用例仍然需要经过校验,关键业务规则也不能完全依赖模型判断。

Logo

更多推荐