Codex 实战:用 Git 分支与 pytest 守住 AI 改代码的底线(2026年8月27日)
1. 一个真实场景:AI 改完代码,你敢直接合并吗
2026年8月27日,我在维护一个 Python 数据处理服务时遇到一个典型问题:需求很简单,给订单导入接口增加一个「按客户 ID 去重」的逻辑。我打开 ChatGPT Plus,把需求贴给 Codex,它很快给出了修改方案,涉及 order_importer.py、customer_service.py 和两个测试文件。
改动看起来合理,但我心里很清楚:AI 生成的代码不能直接信任。它可能改对了目标函数,也可能顺手动了无关的配置;它可能补了测试,也可能测试根本没覆盖边界条件。如果直接合并,回归风险完全不可控。
这篇文章要解决的核心问题就是:如何用 Git 分支 + pytest + 人工审查,给 Codex 的每一次代码修改建立一条可验证、可回退、可审查的流水线。我会用一个真实的 Python 项目案例,从需求说明到合并代码,完整走一遍。
2. 项目背景与目录结构
先看这个项目的结构。它是一个基于 FastAPI 的订单导入服务,使用 pytest 做单元测试,Git 做版本管理。
order-service/
├── app/
│ ├── __init__.py
│ ├── main.py # FastAPI 入口
│ ├── models.py # 数据模型
│ ├── order_importer.py # 订单导入核心逻辑
│ └── customer_service.py # 客户查询服务
├── tests/
│ ├── __init__.py
│ ├── test_order_importer.py
│ └── test_customer_service.py
├── requirements.txt
└── README.md
当前 order_importer.py 的核心逻辑如下:
# app/order_importer.py
from typing import List, Dict
class OrderImporter:
"""订单导入器:负责把外部订单数据写入系统。"""
def __init__(self, customer_service):
self.customer_service = customer_service
def import_orders(self, orders: List[Dict]) -> Dict[str, int]:
"""导入订单列表,返回统计结果。"""
imported = 0
skipped = 0
for order in orders:
customer_id = order.get("customer_id")
if not customer_id:
skipped += 1
continue
# 当前实现:每个客户可以重复导入
self.customer_service.save_order(order)
imported += 1
return {"imported": imported, "skipped": skipped}
需求是:同一个客户 ID 的订单在本次导入中只能出现一次,重复的跳过。这个改动看似简单,但涉及「去重状态放在哪里」「是否影响已有测试」「边界条件怎么覆盖」三个问题,正好适合让 Codex 先出方案,再由我们验证。
3. 第一步:让 Codex 先读项目,不急着改代码
很多开发者拿到需求就直接让 AI 改代码,这是最容易出问题的地方。Codex 对项目上下文理解越充分,改动越精准。我建议第一步先让 Codex 只读不改,输出一份修改计划。
在 ChatGPT Plus 的 Codex 界面中,我使用了下面这个提示词:
请阅读 order-service 项目,重点理解 app/order_importer.py 和
app/customer_service.py 的现有实现,以及 tests/ 下已有测试的覆盖情况。
任务目标:为订单导入增加「同一客户 ID 在本批导入中只处理一次」的去重逻辑。
允许修改范围:仅 app/order_importer.py 及其对应测试文件。
禁止修改:app/main.py、app/models.py、app/customer_service.py、
requirements.txt、配置文件。
请先不要修改任何代码。只输出:
1. 当前实现的问题点
2. 去重逻辑应该放在哪一层
3. 需要新增哪些测试用例
4. 预计会改动哪些文件
这个提示词的关键在于:明确任务目标、划定允许修改范围、禁止修改内容、要求先出计划。Codex 返回的计划里,它建议在 OrderImporter 内部维护一个 seen_customer_ids 集合,而不是改 customer_service,理由是去重是本次导入的临时状态,不应该污染持久层。这个判断是合理的。
4. 第二步:创建 Git 分支,隔离 AI 的改动
在让 Codex 动手之前,先创建一个独立的分支。这样无论 AI 改出什么问题,都不会影响主分支,随时可以丢弃重来。
# 进入项目目录
cd order-service
# 确保主分支是干净的
git status
git checkout main
git pull origin main
# 创建功能分支
git checkout -b feature/codex-deduplicate-orders
# 确认当前分支
git branch --show-current
这里有一个容易被忽略的细节:在创建分支之前,先确认工作区是干净的。如果本地有未提交的改动,AI 修改时会把你的改动和它的改动混在一起,后面审查 git diff 时很难分清哪些是 AI 写的、哪些是你写的。
创建分支后,我再次让 Codex 开始实现,提示词如下:
现在开始实现去重逻辑。请严格遵循你刚才输出的修改计划。
要求:
1. 只修改 app/order_importer.py 和 tests/test_order_importer.py
2. 保持现有函数签名不变
3. 新增测试必须覆盖:正常去重、空列表、缺少 customer_id、重复 customer_id
4. 修改完成后运行 pytest,确保全部测试通过
5. 不要修改任何其他文件
5. 第三步:Codex 的修改结果与测试
Codex 完成修改后,order_importer.py 变成了这样:
# app/order_importer.py
from typing import List, Dict, Set
class OrderImporter:
"""订单导入器:负责把外部订单数据写入系统。"""
def __init__(self, customer_service):
self.customer_service = customer_service
def import_orders(self, orders: List[Dict]) -> Dict[str, int]:
"""导入订单列表,返回统计结果。"""
imported = 0
skipped = 0
seen_customer_ids: Set[str] = set()
for order in orders:
customer_id = order.get("customer_id")
if not customer_id:
skipped += 1
continue
if customer_id in seen_customer_ids:
skipped += 1
continue
seen_customer_ids.add(customer_id)
self.customer_service.save_order(order)
imported += 1
return {"imported": imported, "skipped": skipped}
对应的测试文件新增了去重用例:
# tests/test_order_importer.py
from app.order_importer import OrderImporter
class FakeCustomerService:
"""测试用假服务,记录保存过的订单。"""
def __init__(self):
self.saved_orders = []
def save_order(self, order):
self.saved_orders.append(order)
def test_duplicate_customer_id_skipped():
service = FakeCustomerService()
importer = OrderImporter(service)
orders = [
{"customer_id": "C001", "amount": 100},
{"customer_id": "C001", "amount": 200},
{"customer_id": "C002", "amount": 300},
]
result = importer.import_orders(orders)
assert result == {"imported": 2, "skipped": 1}
assert len(service.saved_orders) == 2
def test_empty_order_list():
service = FakeCustomerService()
importer = OrderImporter(service)
result = importer.import_orders([])
assert result == {"imported": 0, "skipped": 0}
def test_missing_customer_id_skipped():
service = FakeCustomerService()
importer = OrderImporter(service)
orders = [{"amount": 100}, {"customer_id": "C001", "amount": 200}]
result = importer.import_orders(orders)
assert result == {"imported": 1, "skipped": 1}
Codex 在修改完成后自动运行了 pytest,输出显示 3 个测试全部通过。但这里我要强调:AI 说测试通过,不等于真的通过。我们必须自己在终端里再跑一遍,并且要看测试是否真的覆盖了需求。
6. 第四步:人工验证 pytest 与 git diff
在终端里手动运行测试:
cd order-service
python -m pytest tests/ -v
预期输出:
tests/test_order_importer.py::test_duplicate_customer_id_skipped PASSED
tests/test_order_importer.py::test_empty_order_list PASSED
tests/test_order_importer.py::test_missing_customer_id_skipped PASSED
tests/test_customer_service.py::test_save_order PASSED
测试通过后,接下来是最关键的一步:检查 git diff。这一步的目的是确认 AI 只改了它应该改的文件,没有顺手动其他东西。
git diff --stat
git diff app/order_importer.py
git diff tests/test_order_importer.py
git diff --stat 的输出应该只包含两个文件:
app/order_importer.py | 8 +++++++-
tests/test_order_importer.py | 30 ++++++++++++++++++++++++++++++
如果看到 app/main.py 或 requirements.txt 出现在 diff 里,就要警惕了。这说明 Codex 越界修改了无关文件,应该用 git checkout -- <文件> 还原,或者直接丢弃整个分支重来。
7. 第五步:让 Codex 做一次代码审查
除了人工检查,我还会让 Codex 以「审查者」身份再读一遍自己的改动。这一步不是为了让它自我表扬,而是让它从边界条件和异常处理的角度找问题。
请以资深代码审查者的身份,审查当前 Git 分支上的改动。
审查重点:
1. 去重逻辑是否有并发安全隐患
2. 是否有未覆盖的边界条件
3. 异常处理是否完整
4. 是否引入了与需求无关的改动
5. 测试用例是否真正验证了需求
请列出每个问题的严重级别(高/中/低),并给出修改建议。
不要直接修改代码,只输出审查报告。
Codex 的审查报告指出一个中等级别的问题:如果 customer_service.save_order() 抛出异常,seen_customer_ids 里已经记录了该客户 ID,但订单实际没有保存成功。这会导致后续相同客户 ID 的订单被错误跳过。这是一个真实存在的边界问题,值得修复。
修复方案是在 save_order 成功之后再加入 seen_customer_ids,或者捕获异常后回滚。这里我选择让 Codex 修复:
请修复 save_order 抛异常时 seen_customer_ids 状态不一致的问题。
要求:异常时该客户 ID 不应被标记为已处理,且异常应继续向上抛出。
只修改 app/order_importer.py,并补充对应测试。
修复后的核心逻辑:
for order in orders:
customer_id = order.get("customer_id")
if not customer_id:
skipped += 1
continue
if customer_id in seen_customer_ids:
skipped += 1
continue
self.customer_service.save_order(order)
seen_customer_ids.add(customer_id)
imported += 1
同时新增一个测试,模拟 save_order 抛异常的场景:
def test_save_order_exception_not_marked_seen():
class FlakyService(FakeCustomerService):
def save_order(self, order):
if order["customer_id"] == "C001":
raise RuntimeError("db down")
super().save_order(order)
service = FlakyService()
importer = OrderImporter(service)
orders = [
{"customer_id": "C001", "amount": 100},
{"customer_id": "C001", "amount": 200},
]
try:
importer.import_orders(orders)
except RuntimeError:
pass
# 第一次失败后,C001 不应被标记为已处理
assert "C001" not in importer.seen_customer_ids
这里有一个细节:为了让测试能检查内部状态,需要把 seen_customer_ids 从局部变量改为实例属性。这也是 Codex 在修复时主动做的调整,属于合理的重构。
8. 完整工作流:从需求到合并的 Mermaid 流程图
把整个过程画成流程图,方便团队理解这套 AI 辅助开发的标准流程:
这个流程的核心思想是:AI 负责生成和迭代,人负责把关和决策。每一步都有一个明确的验证动作,而不是盲目信任 AI 的输出。
9. 常见错误与解决方法
在实际使用 Codex 的过程中,我遇到过几类高频问题,这里整理出来供参考。
问题一:Codex 修改了无关文件
表现:git diff --stat 里出现与需求无关的文件。
解决方法:用 git checkout -- <文件> 还原该文件,然后在提示词里更严格地限定修改范围,例如「只允许修改 app/order_importer.py 和 tests/test_order_importer.py,其他文件一律禁止改动」。
问题二:测试通过但需求没实现
表现:pytest 全绿,但手动验证发现去重逻辑根本没生效。
原因:AI 写的测试用例本身有缺陷,比如断言写错、或者测试数据没有覆盖真实场景。
解决方法:人工审查测试用例,确认断言与需求一致。不要只看「测试通过」这个结果。
问题三:AI 修改了函数签名,导致调用方报错
表现:order_importer.py 编译通过,但 main.py 调用时报 TypeError。
解决方法:在提示词中明确「保持现有函数签名不变」,并在修改后运行全量测试,而不只是运行单个测试文件。
问题四:敏感信息被写进代码
表现:Codex 在测试代码里硬编码了数据库连接串或 API Key。
解决方法:在提示词中明确「禁止在代码中写入任何密钥、Token 或密码,一律使用环境变量」,并在代码审查时用 grep 检查:
grep -rn "api_key\|password\|secret\|token" app/ tests/
10. 安全注意事项
使用 Codex 辅助开发时,安全底线不能放松。以下几点必须遵守:
第一,不要把 API Key、密码和 Token 写进代码。无论是 AI 生成的代码还是你自己写的代码,敏感配置一律通过环境变量注入。
第二,不向 AI 提交生产环境密码。在提示词和对话中,不要粘贴生产环境的数据库连接串、云服务密钥或任何敏感凭据。
第三,限制 AI 可以修改的文件范围。每次任务都在提示词中明确允许修改和禁止修改的文件,从源头减少越界风险。
第四,AI 生成代码必须经过测试和安全检查。pytest 通过只是最低要求,还要检查是否有 SQL 注入、路径穿越、越权访问等常见安全问题。
第五,不要直接让 AI 修改生产环境。所有改动先在本地分支完成,经过测试和审查后再走正常的发布流程。
11. FAQ
问:ChatGPT Plus 和 ChatGPT Pro 的 Codex 有什么区别?
答:具体功能和可用范围可能随版本、账号和地区变化,请以 OpenAI 当前官方页面为准。一般来说,不同订阅层级在可用额度、模型能力和功能范围上可能有差异,建议以官方说明为准。
问:Codex 生成的测试一定可靠吗?
答:不一定。AI 生成的测试可能存在断言不严谨、覆盖不全、甚至测试本身有 bug 的情况。人工审查测试用例是必须的步骤。
问:每次都要让 Codex 先出计划再改代码吗?
答:对于涉及多个文件或核心逻辑的改动,强烈建议先出计划。对于单行修复这类简单任务,可以适当简化流程,但 Git 分支和测试验证不能省。
问:Codex 修改后测试失败怎么办?
答:把失败信息贴回给 Codex,让它分析原因并修复。同时人工查看失败堆栈,确认是 AI 引入的回归还是原有问题。
问:如何防止 Codex 修改无关代码?
答:在提示词中明确允许和禁止修改的文件列表,修改后用 git diff --stat 核对改动范围,必要时用 git checkout 还原越界文件。
12. 总结
Codex 是一个强大的辅助开发工具,但它不能替代人的判断。这篇文章通过一个订单去重的真实案例,展示了如何用 Git 分支隔离 AI 改动、用 pytest 验证功能正确性、用 git diff 检查改动范围、用双重代码审查发现边界问题。
核心要点可以归纳为四条:
第一,先计划后动手。让 Codex 先读项目、输出修改计划,人工确认后再实施。
第二,分支隔离。所有 AI 改动都在独立分支上进行,随时可以丢弃重来。
第三,测试与 diff 双重验证。pytest 通过不代表一切正常,必须人工检查 git diff 确认没有越界修改。
第四,人工审查不可替代。AI 可以辅助发现边界问题,但最终的安全、质量和业务正确性,仍然需要开发者自己把关。
AI 编程的正确姿势,不是把代码交给 AI 然后祈祷它写对,而是建立一套「AI 生成 → 自动验证 → 人工审查 → 安全合并」的工程化流程。这套流程,才是 AI 辅助开发真正可靠的基础。
葡萄城是专业的软件开发技术和低代码平台提供商,聚焦软件开发技术,以“赋能开发者”为使命,致力于通过表格控件、低代码和BI等各类软件开发工具和服务,一站式满足开发者需求,帮助企业提升开发效率并创新开发模式。
更多推荐



所有评论(0)