ChatGPT Plus 与 Codex 实战:用 AI 编程为 Python 接口编写自动化测试的完整复盘|TaoToken 统一 Key 接入 1. 从手工回归到一键执行Python 接口自动化测试的真实痛点订单查询服务每次发版前测试同学都要在 Postman 里手工点一遍核心接口40 多个接口一轮回归接近两小时断言全靠肉眼比对返回 JSON漏判时有发生。更麻烦的是联调依赖真实数据库数据一脏整轮测试结果就不可信。这个场景在很多 Python 后端团队里反复出现也是我这次决定用 ChatGPT Plus 与 Codex 协作改造 Python 接口自动化测试的直接原因。这篇文章面向正在做接口回归、想用 AI 编程提效但又担心生成代码不可靠的开发者。我会完整复盘从用例设计、断言生成到 pytest 落地的流程给出可复制的 Codex 配置片段与 TaoToken 统一 Key 接入方式并附上本地运行 pytest 的验证动作和失败用例修正步骤。你跟着做能把这套流程迁移到自己的 FastAPI 或 Flask 项目上。需要先明确一点ChatGPT Plus 负责「想清楚测什么」Codex 负责「把测试写出来」人负责「测得对不对」。三者缺一不可。AI 生成测试脚本很快但测试环境隔离、Mock 数据独立性、断言准确性这三件事必须人工把关。下面按实际改造顺序展开。2. TaoToken 统一 Key 接入给 Codex 配一个稳定的模型入口在动手写测试之前先把模型调用链路理顺。我这次用 TaoToken 作为统一入口官网地址是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 地址是 https://taotoken.net/api 。它的作用是让你用一套 Key 访问多个模型避免在 ChatGPT Plus、Codex 之间来回切换账号和配置。如果你用的是 Claude Code 或 Cline 这类支持 MCP 的工具接入方式基本一致填 Base URL、填 Key、选 Model ID。这三件套缺一不可。下面给出一个通用的 settings 片段路径按你本地实际工具调整。{ base_url: https://taotoken.net/api, api_key: sk-你的TaoToken密钥, model: claude-sonnet-4-20250514, timeout: 120 }如果你用的是 Codex 的 auth.json 配置方式可以这样写{ openai: { base_url: https://taotoken.net/api, api_key: sk-你的TaoToken密钥 }, model: gpt-5-codex }注意Base URL 后面不要多加/v1或斜杠否则容易出现 404。Key 建议放在环境变量里不要硬编码进仓库。Model ID 要和你实际调用的模型一致写错会直接报 model not found。配置完成后先用一条最简单的请求验证链路是否通。可以用 curlcurl https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -d { model: gpt-5-codex, messages: [{role: user, content: 回复 ok}] }返回里能看到 choices 字段和内容说明 Key 和 Base URL 都对了。这一步别跳过后面 pytest 跑不通时先回来确认模型入口是否正常能省很多排查时间。3. 可复制配置Codex 生成 pytest 测试脚本的完整片段链路通了之后进入核心环节让 Codex 基于明确的指令生成 pytest 测试脚本。我先把服务的 OpenAPI 描述整理成一段话交给 ChatGPT Plus让它梳理用例矩阵再把矩阵转成 Codex 能执行的生成指令。这样做的原因是直接让 Codex 写测试它容易漏掉边界场景先梳理再生成覆盖度明显更好。第一步是公共请求封装与断言工具。把下面这段放进 conftest.py# 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}) session.allow_redirects False yield session session.close() def assert_ok(resp): assert resp.status_code 200, fHTTP {resp.status_code}: {resp.text} return resp.json() def assert_error(resp, expected_code): assert resp.status_code expected_code, fHTTP {resp.status_code}: {resp.text} return resp.json()这里我特意加了session.allow_redirects False因为 Codex 第一版生成时没处理 3xx 重定向导致断言在重定向场景下误判。这是人工审查必须补的一环。第二步是订单查询接口的测试用例# test_order_query.py from conftest import assert_ok 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_idno_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?start2025-12-31end2026-01-02) data assert_ok(resp) assert data[total] 0第三步是订单创建与取消的异常场景测试# test_order_mutation.py from conftest import assert_error def test_create_order_insufficient_stock(client): payload {product_id: P001, quantity: 99999} resp client.post(/orders, jsonpayload) body assert_error(resp, 409) assert insufficient_stock in body[error] def test_cancel_order_invalid_state(client): payload {order_id: A003, status: COMPLETED} resp client.post(/orders/A003/cancel, jsonpayload) body assert_error(resp, 400) assert invalid_state in body[error]这三段代码 Codex 生成不到三分钟但生成快不代表能用。我逐行审查后发现两个问题一是断言工具没处理重定向二是跨年查询依赖真实数据库CI 环境跑不起来。第二个问题需要引入 Mock 数据源来解决。4. 验证请求与成功结果Mock 数据源 pytest 本地跑通为了让测试不依赖真实数据库我用 Codex 基于 FastAPI 的依赖注入生成了一个内存版 Mock 仓储。这样任何一台机器、任何一次 CI 都能稳定复现同样的结果。# mock_repository.py from dataclasses import dataclass, field dataclass class MockOrderRepository: _orders: dict field(default_factorydict) def seed(self, order_id: str, status: str, user_id: str U001): self._orders[order_id] { order_id: order_id, status: status, user_id: user_id, } 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]然后在测试入口里覆盖依赖# test_main.py import pytest from fastapi.testclient import TestClient from main import app, get_repository from mock_repository import MockOrderRepository pytest.fixture def repo(): r MockOrderRepository() r.seed(A001, PAID) r.seed(A002, PENDING_PAYMENT) return r pytest.fixture def test_client(repo): app.dependency_overrides[get_repository] lambda: repo yield TestClient(app) app.dependency_overrides.clear() def test_query_mock_order(test_client): resp test_client.get(/orders/A001) assert resp.status_code 200 assert resp.json()[status] PAID注意这里把 repo 做成了 fixture而不是模块级单例。原因是模块级单例会让用例之间互相污染前一个用例写入的数据影响后一个用例。这个坑我在改造时踩过后面排障章节会细说。本地运行 pytest 验证pytest tests/ -v --disable-warnings成功时你会看到类似输出tests/test_main.py::test_query_mock_order PASSED tests/test_order_query.py::test_query_by_order_id PASSED tests/test_order_query.py::test_query_by_user_id_empty PASSED tests/test_order_query.py::test_query_by_time_range_cross_year PASSED tests/test_order_mutation.py::test_create_order_insufficient_stock PASSED tests/test_order_mutation.py::test_cancel_order_invalid_state PASSED全部 PASSED 说明 Mock 数据源、断言规则、依赖覆盖都对了。如果某条失败先看断言信息里的 HTTP 状态码和返回体再对照下面的排障章节。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth改造过程中我遇到几类典型报错这里按真实错误信息对照给出排查路径。第一类401 Unauthorized。通常是 Key 没填对或没生效。检查三件事Key 是否放在环境变量里且被正确读取Base URL 是否写成 https://taotoken.net/api 而不是带/v1的地址请求头是否是Authorization: Bearer sk-xxx。如果用的是 Codex auth.json确认字段名是api_key而不是key。第二类local proxy failed。这个报错一般出现在本地网络层和模型服务本身无关。先确认本地没有多余的代理配置干扰请求再检查 Base URL 是否可达。可以用 curl 直接打 https://taotoken.net/api 看返回如果 curl 通而工具不通问题在工具配置。第三类reading choices 相关报错比如KeyError: choices或reading choices。这说明返回体结构和你预期的不一致常见原因是 Model ID 写错或者请求被路由到了不兼容的接口。检查 Model ID 是否和实际调用一致确认请求路径是/v1/chat/completions。第四类OAuth 相关报错。如果你用的是 Claude Code 或 Cline 的 OAuth 登录方式报错通常和 token 过期有关。重新走一遍授权流程或者改用 API Key 方式接入。用 TaoToken 统一 Key 的好处就在这里一套 Key 覆盖多个工具不用每个工具单独维护 OAuth。第五类pytest 收集不到测试文件提示 no tests ran。检查 tests/ 目录下是否有__init__.py文件是否以test_开头函数是否以test_开头。补一个空的__init__.py并显式指定pytest tests/通常能解决。第六类Mock 数据在用例之间互相污染。表现是单独跑某条用例通过一起跑就失败。原因是 repo 是模块级单例状态在用例间共享。解决办法就是上面那样把 repo 改成 pytest fixture每个用例自动重建。6. 语义一致 CTA把测试改造接入你的日常工作流这套流程跑通之后我把它接进了 CI每次发版前自动执行 pytest失败即告警。从用例梳理到 CI 跑通大约用了一天半其中用例矩阵设计和 Mock 数据构造占了大半天脚本生成只占半天剩余时间用于审查和修复问题。这个时间分配本身就说明AI 编程的价值不在于生成速度而在于帮你把重复劳动压缩掉把精力留给判断和把关。如果你也想把这套流程迁移到自己的项目建议先从边界清晰的接口入手把断言补上把环境隔离做好再逐步扩大范围。需要统一模型入口的话可以从 API Keys 页面开始配置https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 里面有各工具的详细配置说明。如果你更想先验证模型输出质量可以到模型对话页面直接试https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。长期做编码和 Agent 任务的话Coding Plan 更适合https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。Claude Code 用户可以直接参考 Anthropic 接入页https://taotoken.net/claude-code-anthropic?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。最后留一个实用技巧每次让 Codex 生成测试脚本后先跑一遍 pytest把失败用例的报错信息原样贴回去让它修比你自己逐行改快得多。但修完必须再跑一遍确认别直接信它的「已修复」。测试代码的可信度最终还是要靠 pytest 的 PASSED 来证明。