2026-08-28|ChatGPT Plus 与 Codex 实战:用 AI 编程为 Python 接口编写自动化测试的完整复盘

1. 引言

2026 年已经过半,AI 编程早已不是「能不能用」的问题,而是「怎么用得稳、用得省」的问题。今天这篇文章,我打算用一次真实的 Python 接口自动化测试案例,完整复盘我是如何借助 ChatGPT Plus 与 Codex 的组合,把一个「靠人肉点点点」的回归测试流程,改造成「一键执行、自动断言、失败即告警」的可靠流水线。

文章会覆盖从测试方案设计、用例生成、Mock 数据构造、问题排查到最终接入 CI 的全过程,包含至少 3 段可运行的代码、一张架构流程图、一份优缺点对比表,以及一个 FAQ 小节。全程不涉及任何充值、代充或购买渠道,请放心阅读。

2. 背景:那个「每次发版都提心吊胆」的接口回归

先交代一下背景。我所在的团队维护着一个基于 Python 3.10 + FastAPI + SQLAlchemy + PostgreSQL 的订单查询服务。接口数量不算多,但每次发版前,回归测试都靠测试同学手工在 Postman 里点:

  • 核心接口超过 40 个,手工回归一轮要 2 小时;
  • 断言全靠肉眼比对返回 JSON,漏判时有发生;
  • 没有 Mock 环境,联调依赖真实数据库,数据一脏就全乱;
  • 每次发版,接口回归都是「提心吊胆」的高危环节。

改造的目标很明确:在不改变接口行为的前提下,把回归测试脚本化、自动化,补上 Mock 与断言,并让测试结果可追溯、可告警。

3. 技术选型:为什么是 ChatGPT Plus + Codex

在开始之前,先说明我这次使用的工具组合:

工具 用途 我的角色
ChatGPT Plus 测试方案设计、用例梳理、断言规则评审 提问者、决策者
Codex 批量生成测试脚本、Mock 数据、CI 配置 指令下达者、审查者
pytest + requests + Git 测试执行、版本控制、人工复核 最终把关人

选择这个组合的原因有三点:

  1. ChatGPT Plus 适合「想清楚」:它的推理能力适合做接口边界梳理、异常场景识别和断言规则设计;
  2. Codex 适合「写出来」:它擅长在明确指令下批量产出测试脚本和 Mock 数据,尤其是机械性的重复代码;
  3. 两者互补:ChatGPT Plus 负责「测什么」,Codex 负责「怎么测」,我负责「测得对不对」。

4. 第一步:用 ChatGPT Plus 做接口清单与用例梳理

改造的第一步不是写代码,而是把要测的接口和场景梳理清楚。我把服务的 OpenAPI 文档整理成一段描述,交给 ChatGPT Plus,让它帮我列出完整的测试用例矩阵。

我给出的原始描述(节选):

text 复制代码
服务包含订单查询、订单创建、订单取消三个核心接口。
订单查询支持按订单号、按用户 ID、按时间范围三种方式;
订单创建需要校验商品库存与用户余额;
订单取消只允许待支付或已支付状态。请帮我梳理测试用例矩阵,并指出边界与异常场景。

ChatGPT Plus 返回的用例梳理结果如下:
#mermaid-svg-GQulxRFp6hq1uzSZ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-GQulxRFp6hq1uzSZ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-GQulxRFp6hq1uzSZ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-GQulxRFp6hq1uzSZ .error-icon{fill:#552222;}#mermaid-svg-GQulxRFp6hq1uzSZ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-GQulxRFp6hq1uzSZ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-GQulxRFp6hq1uzSZ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-GQulxRFp6hq1uzSZ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-GQulxRFp6hq1uzSZ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-GQulxRFp6hq1uzSZ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-GQulxRFp6hq1uzSZ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-GQulxRFp6hq1uzSZ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-GQulxRFp6hq1uzSZ .marker.cross{stroke:#333333;}#mermaid-svg-GQulxRFp6hq1uzSZ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-GQulxRFp6hq1uzSZ p{margin:0;}#mermaid-svg-GQulxRFp6hq1uzSZ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-GQulxRFp6hq1uzSZ .cluster-label text{fill:#333;}#mermaid-svg-GQulxRFp6hq1uzSZ .cluster-label span{color:#333;}#mermaid-svg-GQulxRFp6hq1uzSZ .cluster-label span p{background-color:transparent;}#mermaid-svg-GQulxRFp6hq1uzSZ .label text,#mermaid-svg-GQulxRFp6hq1uzSZ span{fill:#333;color:#333;}#mermaid-svg-GQulxRFp6hq1uzSZ .node rect,#mermaid-svg-GQulxRFp6hq1uzSZ .node circle,#mermaid-svg-GQulxRFp6hq1uzSZ .node ellipse,#mermaid-svg-GQulxRFp6hq1uzSZ .node polygon,#mermaid-svg-GQulxRFp6hq1uzSZ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-GQulxRFp6hq1uzSZ .rough-node .label text,#mermaid-svg-GQulxRFp6hq1uzSZ .node .label text,#mermaid-svg-GQulxRFp6hq1uzSZ .image-shape .label,#mermaid-svg-GQulxRFp6hq1uzSZ .icon-shape .label{text-anchor:middle;}#mermaid-svg-GQulxRFp6hq1uzSZ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-GQulxRFp6hq1uzSZ .rough-node .label,#mermaid-svg-GQulxRFp6hq1uzSZ .node .label,#mermaid-svg-GQulxRFp6hq1uzSZ .image-shape .label,#mermaid-svg-GQulxRFp6hq1uzSZ .icon-shape .label{text-align:center;}#mermaid-svg-GQulxRFp6hq1uzSZ .node.clickable{cursor:pointer;}#mermaid-svg-GQulxRFp6hq1uzSZ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-GQulxRFp6hq1uzSZ .arrowheadPath{fill:#333333;}#mermaid-svg-GQulxRFp6hq1uzSZ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-GQulxRFp6hq1uzSZ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-GQulxRFp6hq1uzSZ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-GQulxRFp6hq1uzSZ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-GQulxRFp6hq1uzSZ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-GQulxRFp6hq1uzSZ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-GQulxRFp6hq1uzSZ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-GQulxRFp6hq1uzSZ .cluster text{fill:#333;}#mermaid-svg-GQulxRFp6hq1uzSZ .cluster span{color:#333;}#mermaid-svg-GQulxRFp6hq1uzSZ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-GQulxRFp6hq1uzSZ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-GQulxRFp6hq1uzSZ rect.text{fill:none;stroke-width:0;}#mermaid-svg-GQulxRFp6hq1uzSZ .icon-shape,#mermaid-svg-GQulxRFp6hq1uzSZ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-GQulxRFp6hq1uzSZ .icon-shape p,#mermaid-svg-GQulxRFp6hq1uzSZ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-GQulxRFp6hq1uzSZ .icon-shape .label rect,#mermaid-svg-GQulxRFp6hq1uzSZ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-GQulxRFp6hq1uzSZ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-GQulxRFp6hq1uzSZ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-GQulxRFp6hq1uzSZ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 接口测试用例设计
正常路径
边界场景
异常场景
按订单号查询
按用户 ID 查询
按时间范围查询
空结果集
分页边界
时间范围为空
订单不存在
库存不足
非法状态流转

这一步的价值在于:把隐性的测试盲区显性化。在梳理过程中,ChatGPT Plus 还指出了两个我原先没注意到的边界场景------「时间范围跨年」和「订单号包含前导零」,这两条在旧的手工回归里从来没人测过。

5. 第二步:用 Codex 生成接口测试脚本

用例矩阵梳理清楚之后,就轮到 Codex 上场了。我给它下达了明确的生成指令,要求产出基于 pytest + requests 的测试脚本。

第一段代码:公共请求封装与断言工具。

python 复制代码
# conftest.py
import pytest
import requests

BASE_URL = "http://localhost:8000"

@pytest.fixture
def client():
    session = requests.Session()
    session.headers.update({"Content-Type": "application/json"})
    yield session
    session.close()

def assert_ok(resp):
    assert resp.status_code == 200, f"HTTP {resp.status_code}: {resp.text}"
    return resp.json()

def assert_error(resp, expected_code):
    assert resp.status_code == expected_code, f"HTTP {resp.status_code}: {resp.text}"

第二段代码:订单查询接口的测试用例。

python 复制代码
# test_order_query.py
def test_query_by_order_id(client):
    resp = client.get("/orders/A001")
    data = assert_ok(resp)
    assert data["order_id"] == "A001"
    assert data["status"] in {"PENDING_PAYMENT", "PAID", "SHIPPED"}

def test_query_by_user_id_empty(client):
    resp = client.get("/orders?user_id=no_such_user")
    data = assert_ok(resp)
    assert data["items"] == []
    assert data["total"] == 0

def test_query_by_time_range_cross_year(client):
    resp = client.get("/orders?start=2025-12-31&end=2026-01-02")
    data = assert_ok(resp)
    assert data["total"] >= 0

第三段代码:订单创建与取消的异常场景测试。

python 复制代码
# test_order_mutation.py
def test_create_order_insufficient_stock(client):
    payload = {"product_id": "P001", "quantity": 99999}
    resp = client.post("/orders", json=payload)
    assert_error(resp, 409)
    assert "insufficient_stock" in resp.json()["error"]

def test_cancel_order_invalid_state(client):
    payload = {"order_id": "A003", "status": "COMPLETED"}
    resp = client.post("/orders/A003/cancel", json=payload)
    assert_error(resp, 400)
    assert "invalid_state" in resp.json()["error"]

Codex 生成这三段代码只花了不到三分钟。但请注意:生成快不代表可以直接用。我逐行审查后发现两个问题:

  1. 第一段代码里 assert_ok 没有处理 2xx 之外的 3xx 重定向,需要显式关闭 allow_redirects
  2. 第二段代码的跨年查询依赖真实数据库,需要改成 Mock 数据源,否则 CI 环境跑不起来。

这两个问题说明:AI 生成代码必须经过人工审查和适配,尤其是测试环境的隔离性。

6. 第三步:用 Codex 构造 Mock 数据源

为了让测试不依赖真实数据库,我让 Codex 基于 FastAPI 的依赖注入生成一个内存版 Mock 仓储。

python 复制代码
# mock_repository.py
from dataclasses import dataclass, field

@dataclass
class MockOrderRepository:
    _orders: dict = field(default_factory=dict)

    def seed(self, order_id: str, status: str):
        self._orders[order_id] = {"order_id": order_id, "status": status}

    def find_by_id(self, order_id: str):
        return self._orders.get(order_id)

    def find_by_user(self, user_id: str):
        return [o for o in self._orders.values() if o.get("user_id") == user_id]
python 复制代码
# test_main.py
from fastapi.testclient import TestClient
from main import app
from mock_repository import MockOrderRepository

repo = MockOrderRepository()
repo.seed("A001", "PAID")
repo.seed("A002", "PENDING_PAYMENT")

app.dependency_overrides[get_repository] = lambda: repo
client = TestClient(app)

def test_query_mock_order():
    resp = client.get("/orders/A001")
    assert resp.status_code == 200
    assert resp.json()["status"] == "PAID"

Mock 数据源跑通之后,我把真实数据库的依赖彻底从测试里摘除,确保任何一台机器、任何一次 CI 都能稳定复现同样的结果。

7. 第四步:问题排查实录

改造过程中并非一帆风顺,这里记录两个典型的排查过程。

7.1 问题一:pytest 收集不到测试文件

把测试脚本放进 tests/ 目录后,运行 pytest 提示 no tests ran。排查步骤:

  1. 检查目录结构:确认 tests/ 下没有 __init__.py,pytest 默认递归收集需要它;
  2. 检查命名规则:确认文件以 test_ 开头、函数以 test_ 开头;
  3. 解决方案:在 tests/ 下补一个空的 __init__.py,并显式指定 pytest tests/
bash 复制代码
pytest tests/ -v --disable-warnings

7.2 问题二:Mock 数据在用例之间互相污染

多个测试用例共用同一个 MockOrderRepository 实例,导致前一个用例写入的数据影响后一个用例。排查步骤:

  1. 复现:连续运行两个用例,第二个用例断言失败;
  2. 定位:repo 是模块级单例,状态在用例间共享;
  3. 解决:把 repo 改成 pytest fixture,每个用例自动重建。
python 复制代码
@pytest.fixture
def repo():
    r = MockOrderRepository()
    r.seed("A001", "PAID")
    return r

8. 优缺点分析

这次「ChatGPT Plus + Codex」组合改造,我整理了一份客观的优缺点对比:

维度 优点 缺点
效率 测试脚本生成速度快,重复劳动大幅减少 生成结果仍需人工逐行审查
质量 用例矩阵梳理完整,边界场景覆盖补全 对测试环境隔离性不敏感
成本 无需额外购买工具,现有订阅即可 提示词设计需要学习成本
风险 可快速产出多种测试方案对比 过度信任 AI 输出会引入隐患

9. FAQ

Q1:ChatGPT Plus 和 Codex 有什么区别?

ChatGPT Plus 更偏向对话式推理与方案设计,适合做用例梳理、断言规则评审和知识问答;Codex 更偏向在明确指令下批量生成和修改代码,适合执行具体的编码任务。两者可以配合使用。

Q2:AI 生成的测试代码可以直接进 CI 吗?

不建议。AI 生成代码必须经过人工审查、本地运行和代码评审,尤其要注意测试环境隔离、Mock 数据独立性和断言准确性。

Q3:这次改造花了多长时间?

从用例梳理到 CI 跑通,大约用了一天半。其中用例矩阵设计和 Mock 数据构造占了大半天,脚本生成只占半天,剩余时间用于审查和修复问题。

Q4:没有 AI 编程经验的人能上手吗?

可以。建议从小的、边界清晰的接口开始练习,先学会「把测试场景讲清楚」,再逐步尝试更复杂的测试改造任务。

10. 总结

这次改造让我对 AI 编程有了更务实的认识:AI 不是替代测试工程师,而是放大测试工程师的生产力。ChatGPT Plus 帮我「想清楚」,Codex 帮我「写出来」,而我负责「把好关」。

如果你也在考虑用 AI 编程改造接口测试,我的建议是:先从用例矩阵、Mock 数据这类边界清晰的模块入手,把断言补上,把环境隔离做好,再逐步扩大范围。AI 编程的价值,不在于一次生成多少测试代码,而在于帮你把那些「每次发版都提心吊胆」的回归流程,变成「一键执行、自动断言、失败即告警」的可靠流水线。

相关推荐
小白的成长路程14 分钟前
AI答案被竞品垄断,如何破局
人工智能·geo
天远Date Lab15 分钟前
零信任架构实战:基于天远天远风控经营异常预警构建分布式企业合规监控网关
人工智能·分布式·架构
是店小二呀17 分钟前
AI 已经能直接改设计稿了:Windows部署Penpot,接入Codex MCP实测
人工智能
DeepIntelli17 分钟前
GEO 服务商选型:引用溯源与效果监测该看哪些工程能力
人工智能·chatgpt
ChaITSimpleLove19 分钟前
用 .NET 10 技术栈 “玩转 AI 学习” 的路线图:从 Minimal API 到 AI Agent 产品
人工智能·.net·学习 ai
pjj1985419 分钟前
深度学习-神经网络是如何训练的
人工智能·深度学习·神经网络
zcmodeltech19 分钟前
垃圾发电厂沙盘模型控制系统设计与实现:多设备协同联动方案
java·大数据·数据库·人工智能·stm32·嵌入式硬件·制造
蓝悦无人机21 分钟前
端侧神经网络量化(二):训练后量化(PTQ)
人工智能·pytorch·深度学习·神经网络·边缘计算
2603_9663027524 分钟前
2026企业AI办公工具选型全指南
人工智能