Codex 实战:用 Git 分支与 pytest 守住 AI 修改的代码质量(2026年8月27日)
1. 从一个真实问题说起
上周我在维护一个 FastAPI 项目时,遇到一个典型场景:需求方要求给订单接口增加一个“按状态筛选”的参数。这个改动本身不大,但项目里订单查询逻辑分散在 service 层和 repository 层,还牵扯到几个历史遗留的边界条件。我决定用 Codex 来辅助完成这次修改,同时给自己定了一条纪律:AI 改代码可以,但必须走完整的 Git 分支 + 自动化测试流程,改完必须人工审查 git diff。
这篇文章就围绕这个真实案例展开,记录我如何使用 Codex 完成“需求分析 → 定位代码 → 制定计划 → 创建分支 → 修改代码 → 补充测试 → 运行测试 → 检查 diff → 人工审查 → 合并”的完整闭环。如果你也在用 Codex 辅助日常开发,希望这套流程能帮你减少“AI 改坏了但没发现”的风险。
先说明一点:Codex 是 OpenAI 提供的 AI 编程助手,具体功能、可用范围和套餐权益可能随版本、账号和地区变化,请以 OpenAI 当前官方页面为准。本文重点不是介绍 Codex 的功能清单,而是分享一套可复制的工程实践。
2. 项目背景与需求描述
为了演示,我准备了一个精简但完整的 FastAPI 项目,目录结构如下:
order-service/
├── app/
│ ├── __init__.py
│ ├── main.py
│ ├── models.py
│ ├── schemas.py
│ ├── repository.py
│ └── service.py
├── tests/
│ ├── __init__.py
│ ├── conftest.py
│ └── test_order_service.py
├── requirements.txt
└── README.md
核心需求是:给 GET /orders 接口增加一个可选的 status 查询参数,支持 pending、paid、shipped、cancelled 四种状态,不传时返回全部订单。
这个需求看似简单,但有几个隐藏的坑:
- repository 层目前只有一个
list_all()方法,没有按条件过滤的能力; - service 层直接透传 repository 结果,没有参数校验;
- 现有测试只覆盖了“返回全部订单”的场景,没有覆盖过滤逻辑;
- 如果直接让 AI 改,它可能会顺手重构其他无关代码,这正是我们最需要防范的。
3. 用 Codex 分析项目,先不急着改代码
很多人在使用 Codex 时犯的第一个错误,就是上来就让它“帮我加个功能”。Codex 确实能直接改,但如果没有先让它理解项目结构,它可能会改错文件、改错逻辑,甚至引入无关改动。
我的做法是:第一步只让 Codex 读代码、输出分析,不修改任何文件。
下面是我实际使用的提示词:
请分析当前项目 order-service 的代码结构,重点回答以下问题,不要修改任何文件:
1. GET /orders 接口从入口到数据返回的完整调用链是什么?
2. repository 层目前提供了哪些查询方法?是否支持按状态过滤?
3. service 层是否对查询参数做了校验?
4. 现有测试覆盖了哪些场景?缺少哪些场景?
5. 如果要增加 status 查询参数,涉及哪些文件、哪些函数?
输出要求:
- 列出每个相关文件的路径和关键函数名
- 指出当前实现中可能影响新需求的边界条件
- 不要给出修改建议,只做现状分析
这个提示词的关键在于:明确“不要修改任何文件”,并且要求 Codex 输出文件路径和函数名,方便我后续核对。
Codex 返回的分析结果大致如下(我做了精简):
调用链:main.py 中 GET /orders 路由 → service.py 的 list_orders() → repository.py 的 list_all()
repository.list_all() 当前直接返回内存列表,无过滤参数
service.list_orders() 未对 query 参数做任何校验
tests/test_order_service.py 仅覆盖 list_all 返回全部订单的场景
涉及文件:app/main.py、app/service.py、app/repository.py、tests/test_order_service.py
这一步的价值在于:在动手之前,先确认 AI 对项目的理解是否准确。 如果 Codex 的分析结果和实际代码不符,说明它没有正确理解项目,这时候就不应该继续让它改代码。
4. 让 Codex 制定修改计划,再创建 Git 分支
分析完成后,我让 Codex 输出一份具体的修改计划,仍然不修改代码:
基于你刚才的分析,请为“增加 status 查询参数”这个需求制定一份修改计划,包含:
1. 每个需要修改的文件,以及具体的修改点(函数名、改动内容)
2. 新增测试用例的清单,包括正常场景和边界场景
3. 明确禁止修改的文件和代码范围
4. 修改完成后需要运行的测试命令
注意:
- 只允许修改 app/main.py、app/service.py、app/repository.py、tests/test_order_service.py
- 禁止修改 models.py、schemas.py、requirements.txt
- 不要重构与 status 过滤无关的代码
- 不要改动现有测试的断言逻辑
Codex 给出的计划大致如下:
1. app/repository.py:给 list_all() 增加可选参数 status,内部按状态过滤
2. app/service.py:list_orders() 增加 status 参数,并校验取值合法性
3. app/main.py:路由中读取 status 查询参数,传给 service
4. tests/test_order_service.py:新增 4 个测试用例(每种状态各一个)+ 1 个非法状态用例
5. 运行 pytest tests/ -v 验证
拿到计划后,我先人工确认这份计划没有越界,然后创建 Git 分支:
git checkout -b feature/order-status-filter
这里有一个重要的工程原则:AI 修改代码之前,一定要先创建独立的分支。 这样即使 AI 改坏了,也不会影响主分支,随时可以丢弃重来。
5. 让 Codex 实现功能并补充测试
计划确认后,我让 Codex 开始实现。这一步的提示词要明确“允许修改范围”和“禁止修改内容”,并且要求它同时补充测试:
请按照刚才确认的计划实现 status 查询参数功能,并同步补充测试。
允许修改的文件:
- app/repository.py
- app/service.py
- app/main.py
- tests/test_order_service.py
禁止修改的文件:
- app/models.py
- app/schemas.py
- requirements.txt
- README.md
实现要求:
1. repository.list_all() 增加 status 参数,status 为 None 时返回全部
2. service.list_orders() 校验 status 取值,非法值返回 400
3. main.py 路由读取 status 查询参数
4. 测试覆盖:四种合法状态各一个用例、不传 status 返回全部、非法 status 返回 400
5. 不要修改与本次需求无关的代码
完成后运行 pytest tests/ -v,确保所有测试通过。
Codex 修改后的核心代码大致如下。
app/repository.py:
from typing import List, Optional
from app.models import Order
VALID_STATUSES = {"pending", "paid", "shipped", "cancelled"}
class OrderRepository:
def __init__(self) -> None:
self._orders: List[Order] = [
Order(id=1, customer="张三", status="pending"),
Order(id=2, customer="李四", status="paid"),
Order(id=3, customer="王五", status="shipped"),
Order(id=4, customer="赵六", status="cancelled"),
]
def list_all(self, status: Optional[str] = None) -> List[Order]:
if status is None:
return self._orders
return [order for order in self._orders if order.status == status]
app/service.py:
from typing import List, Optional
from fastapi import HTTPException
from app.repository import OrderRepository, VALID_STATUSES
class OrderService:
def __init__(self) -> None:
self._repo = OrderRepository()
def list_orders(self, status: Optional[str] = None) -> List[dict]:
if status is not None and status not in VALID_STATUSES:
raise HTTPException(status_code=400, detail=f"非法状态值: {status}")
orders = self._repo.list_all(status=status)
return [order.dict() for order in orders]
app/main.py:
from typing import Optional
from fastapi import FastAPI
from app.service import OrderService
app = FastAPI()
service = OrderService()
@app.get("/orders")
def list_orders(status: Optional[str] = None):
return service.list_orders(status=status)
tests/test_order_service.py 中新增的测试:
import pytest
from fastapi.testclient import TestClient
from app.main import app
client = TestClient(app)
def test_list_orders_without_status_returns_all():
resp = client.get("/orders")
assert resp.status_code == 200
assert len(resp.json()) == 4
@pytest.mark.parametrize("status", ["pending", "paid", "shipped", "cancelled"])
def test_list_orders_with_valid_status(status):
resp = client.get("/orders", params={"status": status})
assert resp.status_code == 200
assert len(resp.json()) == 1
assert resp.json()[0]["status"] == status
def test_list_orders_with_invalid_status_returns_400():
resp = client.get("/orders", params={"status": "unknown"})
assert resp.status_code == 400
这里需要说明几点:
VALID_STATUSES定义在 repository 层,service 层引用它做校验,避免两处维护同一份状态列表;- 非法状态返回 400 而不是静默忽略,这样前端能明确感知参数错误;
- 测试用
parametrize覆盖四种合法状态,避免写四个重复的测试函数。
6. 运行测试并检查 git diff
Codex 修改完成后,我并没有直接信任结果,而是先运行测试:
cd order-service
pytest tests/ -v
预期输出:
tests/test_order_service.py::test_list_orders_without_status_returns_all PASSED
tests/test_order_service.py::test_list_orders_with_valid_status[pending] PASSED
tests/test_order_service.py::test_list_orders_with_valid_status[paid] PASSED
tests/test_order_service.py::test_list_orders_with_valid_status[shipped] PASSED
tests/test_order_service.py::test_list_orders_with_valid_status[cancelled] PASSED
tests/test_order_service.py::test_list_orders_with_invalid_status_returns_400 PASSED
全部通过后,接下来是最关键的一步:检查 git diff,确认 AI 没有改无关代码。
git diff --stat
git diff app/
git diff --stat 会列出所有被修改的文件和改动行数。如果发现计划之外的文件被改动,比如 models.py 或 requirements.txt,就要立即警惕。
我实际检查时发现,Codex 确实只改了计划内的四个文件,没有越界。但我也遇到过 Codex 顺手“优化”了其他代码的情况,所以这一步绝对不能省。
7. 用 Codex 做代码审查,再人工复核
测试通过、diff 干净之后,我还会让 Codex 以“审查者”身份再检查一遍自己的修改。这一步的目的是让 AI 从另一个角度发现问题:
请以资深代码审查者的身份,审查当前分支上 git diff 的改动,重点检查:
1. 是否有逻辑错误或边界条件遗漏?
2. 是否有安全风险(如参数注入、敏感信息泄露)?
3. 是否有与本次需求无关的改动?
4. 测试是否覆盖了关键场景?有没有遗漏?
5. 代码风格是否与项目现有代码一致?
输出格式:
- 每个问题标注严重程度(高/中/低)
- 给出具体文件和行号
- 只报告问题,不要直接修改代码
Codex 的审查结果通常能发现一些我忽略的细节。比如有一次它指出:service 层直接暴露了 VALID_STATUSES 的引用,如果后续状态列表变化,可能导致 service 和 repository 耦合过紧。虽然这个建议不一定需要立即采纳,但至少提醒我关注设计层面的问题。
但请注意:AI 的审查结果不能替代人工审查。 我会逐行阅读 git diff,确认每个改动都符合预期,尤其是:
- 是否有硬编码的敏感信息(API Key、密码、Token);
- 是否有异常处理被吞掉;
- 是否有日志打印了不该打印的数据;
- 是否有性能隐患(比如在循环里查数据库)。
8. 合并分支与后续验证
人工审查通过后,合并分支:
git checkout main
git pull origin main
git merge feature/order-status-filter
git push origin main
合并后,我还会在本地再跑一次完整测试,确认合并没有引入冲突:
pytest tests/ -v
如果项目配置了 GitHub Actions,可以在推送后自动触发 CI,进一步验证。下面是一个简单的 CI 配置示例:
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install -r requirements.txt
- run: pytest tests/ -v
这个配置会在每次推送或 PR 时自动安装依赖并运行测试。如果测试失败,CI 会直接标红,避免坏代码合入主分支。
9. 常见错误与解决方法
在使用 Codex 配合 Git 和 pytest 的过程中,我总结了几类常见问题。
问题一:Codex 修改了计划外的文件。
解决方法:在提示词中明确列出“允许修改的文件”和“禁止修改的文件”,修改后必须用 git diff --stat 核对。如果发现越界改动,用 git checkout -- <file> 还原该文件。
问题二:测试通过但功能实际不符合需求。
解决方法:测试只能验证“代码按测试的预期运行”,不能验证“需求理解是否正确”。建议在让 Codex 实现之前,先让它输出对需求的理解,人工确认后再动手。
问题三:Codex 生成的测试覆盖不足。
解决方法:在提示词中明确要求覆盖边界场景,比如非法参数、空数据、超长字符串等。修改后人工检查测试用例,必要时自己补充。
问题四:AI 修改破坏了原有功能。
解决方法:确保修改前已有完整的测试基线。如果项目测试覆盖不足,先让 Codex 补测试,再让它改功能。
10. 安全注意事项
无论 Codex 多强大,安全底线必须由开发者自己守住:
- 不要把 API Key、密码和 Token 写进代码,使用环境变量保存敏感配置;
- 不向 AI 提交生产环境密码,提示词中不要粘贴真实凭据;
- 检查日志和配置文件中的敏感信息,AI 生成的日志代码可能无意中打印敏感数据;
- 限制 AI 可以修改的文件范围,在提示词中明确禁止修改配置文件、密钥文件;
- 使用最小权限原则,本地开发账号不要使用生产权限;
- AI 生成代码必须经过测试和安全检查,不能因为测试通过就放松警惕;
- 不要直接让 AI 修改生产环境,所有改动先走分支、测试、审查、合并流程。
11. FAQ
Q1:Codex 和 ChatGPT Plus / ChatGPT Pro 是什么关系?
Codex 是 OpenAI 的 AI 编程助手,ChatGPT Plus 和 ChatGPT Pro 是 ChatGPT 的订阅套餐。具体哪些套餐包含 Codex 访问权限、额度如何计算,请以 OpenAI 当前官方页面为准。
Q2:每次让 Codex 改代码都要走完整流程吗?
小改动可以简化,但涉及核心逻辑、数据层或生产代码时,建议至少走“分支 + 测试 + diff 检查”三步。
Q3:Codex 生成的测试可以直接信任吗?
不能。AI 生成的测试可能只覆盖了“让它通过”的场景,而不是“覆盖需求”的场景。人工审查测试用例是必须的。
Q4:如果 Codex 改坏了代码怎么办?
只要在独立分支上操作,直接丢弃分支即可:git checkout main && git branch -D feature/xxx。
12. 总结
Codex 是一个强大的辅助工具,但它不能替代开发者的判断。通过“需求说明 → AI 阅读项目 → 定位相关文件 → 制定修改计划 → 创建 Git 分支 → 修改代码 → 编写测试 → 运行测试 → 检查 git diff → 人工代码审查 → 合并代码”这套流程,我可以在享受 AI 效率的同时,把风险控制在可控范围内。
核心经验可以总结为三点:
- 先分析、后动手:让 Codex 先输出对项目的理解和修改计划,人工确认后再实现;
- 用分支隔离风险:AI 的所有改动都放在独立分支上,随时可以丢弃;
- 测试和审查不可省:pytest 验证功能正确性,
git diff和人工审查验证改动范围。
AI 是辅助开发工具,最终代码仍然需要测试和人工审查。希望这篇文章能帮你建立一套更可靠的 Codex 工作流。
葡萄城是专业的软件开发技术和低代码平台提供商,聚焦软件开发技术,以“赋能开发者”为使命,致力于通过表格控件、低代码和BI等各类软件开发工具和服务,一站式满足开发者需求,帮助企业提升开发效率并创新开发模式。
更多推荐



所有评论(0)