让 Codex 写测试:先问这条测试能发现什么错误 - AIPlan
AI 生成测试时怎样保证有意义?从用户行为、边界和回归风险选样本,验证测试能捕获问题,区分真实覆盖与重复实现。

一条测试的价值等于它能发现的错误数量。写之前先回答一个问题:如果我故意把这段代码改坏,这条测试会不会失败?答不上来,这条测试只是把实现抄了一遍。截至 2026 年 10 月 18 日核对,这条判断标准比覆盖率数字有用得多。
测试抄实现是最常见的问题
让它给一个计算函数写测试,常常得到这样的结果:
def test_discount():
assert calculate_discount(100, 0.2) == 80
这条测试能通过,也能在你把 0.2 改成 0.3 时失败。但如果你把整个函数逻辑改成 return price * 0.8,测试照样通过。它测的是这一个输入的结果,不是规则。
改法是把规则写出来:
def test_discount_never_exceeds_price():
for rate in [0, 0.1, 0.5, 0.99]:
assert 0 <= calculate_discount(100, rate) <= 100
四条输入覆盖了边界,任何让结果超过原价的改动都会被抓住。
三类输入值得优先写
选样本的优先级很清楚:
• 边界值:0、-1、空字符串、空列表、最大长度、最大金额
• 用户能触发的异常:余额不足、库存为 0、优惠券过期、重复提交
• 最近改过的地方:过去 3 个月修过的 bug 都补一条
第 3 条是回报最高的。一个修过的 bug 说明这条路径曾经出过问题,回归的概率比随机路径高。
第 1 条里的边界值有个通用清单:数字取 0、1、-1、上限、上限加 1;字符串取空串、1 个字符、超长;列表取空、1 项、超大。5 类乘 5 类,能生成 25 个基础样本,实际挑 8 到 10 个就够。
覆盖率的数字意义有限
行覆盖率 80% 意味着 80% 的代码被执行过,不意味着 80% 的行为被验证过。一个函数可以每行都跑到,但所有断言都只检查“不抛异常”。
判断真实覆盖的方式:把被测函数里的每个 if 分支列出来,每个分支至少一条测试。分支数比行数更能反映测试的有效性。
如果项目已经在跑覆盖率工具,可以把它当筛查工具用:覆盖率低的模块优先补,覆盖率高的模块抽查断言是否有效。别把覆盖率当 KPI,那会催生一堆没有断言的空测试。
让 Codex 自己验证测试有效
这条做法可以显著提高产出质量:写完测试后,让它故意改坏一处实现,跑测试,确认测试失败,再改回来。
任务描述:
写完测试后做一次验证:
1. 把 validateOrder 里的 `if amount <= 0` 改成 `if amount < 0`
2. 跑测试,确认有测试失败
3. 把改动恢复
4. 再跑一次,确认全部通过
把两次的输出贴出来。
能通过这个验证的测试才真的在守着行为。跑不通说明测试写得太松。
测试文件不能为了让代码通过而改
有一类失败模式很隐蔽:代码改错了,测试也改错了,两边一起变得“正确”。常见于让模型同时改实现和测试的任务。
防线是分开两轮:
• 第一轮只写测试,不改实现。跑一遍,确认该失败的失败、该通过的通过。
• 第二轮改实现,测试文件不动。
如果第二轮必须改测试,先停下来问为什么。三种可能:需求变了(合理,但要走单独一轮)、测试写错了(合理,但要说明)、代码改错了在迁就(危险)。
/review 有个 Review uncommitted changes 范围,能一次看清实现和测试的改动比例。测试改动超过实现改动的时候,值得逐行看。
测试命名要写出行为
test_order_1、test_case_2 这类命名三个月后没人看得懂。改成一句话描述行为:
重复提交同一订单返回相同结果,不产生第二条记录
优惠券过期时返回 409 而非 400
金额为 0 时不创建订单
名字里带具体条件,失败时输出一行就能知道哪条规则破了。很多测试框架会把测试名直接打到失败日志里,命名质量直接决定排查速度。
一条测试只验一件事
一条测试里断言 8 个字段,失败时你不知道是哪个字段的问题,还得逐行读。拆成 3 条各验一组,失败信息直接指向问题。
例外是“整体结构”类的断言,比如返回 JSON 的 schema 校验,这种本来就该一次性验完。判断标准:失败时你需要几个信息才能定位,超过 1 个就拆。
回归测试怎么写进流程
修 bug 的标准动作是“先写一条能复现的失败测试,再修到它通过”。这条测试天然成为回归测试,因为它在修复前一定是红的。
实际执行顺序:
• 用那四步复现格式把 bug 写清楚
• 写一条测试,跑起来,确认失败
• 修实现
• 再跑,确认通过
• 打检查点
第 2 步是关键。跳过它直接修,修完补的测试可能只是“把当前行为写下来”,抓不住原来那个问题。修 bug 的完整流程在 用 Codex 修 bug:先验证原因,再动实现。
什么时候不写测试
三种情况写了也没用:
• 一次性的数据迁移脚本,跑完就删
• 纯配置文件的格式调整
• 界面纯样式改动,没有逻辑分支
反过来,下面这些即使看起来简单也值得写:金额计算、状态流转、权限判断、去重逻辑。这四类出问题时的代价远高于写测试的成本。
一次规模和时间
给一个 300 行的业务模块补测试,典型分配:
• 读代码列分支:20 分钟
• 写 8 到 12 条测试:40 分钟
• 做故意改坏的验证:15 分钟
• 修测试本身的错误:15 分钟
合计 90 分钟。写测试的时间通常比预想的长,写的过程本身就会暴露实现里没处理的边界。
这个工作量用 Codex 做,读取集中在 1 到 2 个文件,额度消耗很低。Plus 的 $20 折算约 ¥133.80,一周给 3 到 4 个模块补测试不成问题,¥115.50 的 Plus 月卡 走的是同一条通道。测试和重构经常一起做,那部分的任务组织方式在 用 Codex 重构代码:替换旧实现,也要清理旧路径。
下次让 Codex 写测试时,先加那句“改坏一处,确认测试失败”。省下的不是写测试的时间,是三个月后排查回归的时间。
参考资料资料核对日期:2026-10-17
• Codex CLI
• Code review
延伸阅读• 用 Codex 修 bug:先验证原因,再动实现
• 用 Codex 重构代码:替换旧实现,也要清理旧路径
• 用 Codex 做代码审查:每个发现都要说明怎样触发
• 用 Codex 重做前端,先列清楚页面和交易流程