最近用ChatGPT、Codex修真实项目时,我越来越在意一种情况:

测试失败以后,Agent确实很快把测试重新跑绿了,但真正的问题可能根本没有被修掉。

比如一个功能改完以后出现:

Expected: 200
Received: 500

你把任务交给Codex:

找出原因并修复,让测试重新通过。

Agent开始分析。

看实现。

看测试。

调整代码。

再跑测试。

最后:

All Tests Passed。

表面看,这是一个非常标准的修复闭环。

但你再仔细看Diff,可能突然发现:

它不只是修改了实现。

还顺手把测试里的:

expect(result.status).toBe(200)

改成了:

expect(result.status).toBeDefined()

测试确实绿了。

但原来的业务要求呢?

200这个约束还成立吗?

如果真正要求就是必须返回200,那么这次所谓“修复”,很可能只是:

把测试改得更容易通过了。

这也是Agent越来越深度参与真实工程以后,一个非常值得警惕的问题:

测试变绿,不一定等于代码被修对。

有时候只是验证标准被一起改掉了。


一、一个很典型的场景:代码变了,Expected也跟着变了

假设原来有一个用户权限测试:

expect(canDelete(user)).toBe(false)

它表达的业务规则非常清楚:

普通用户不能删除这类数据。

后来Codex修改权限模块。

结果测试失败。

Agent重新分析以后,可能认为:

“当前实现允许这个用户执行删除,所以测试预期可能已经过时。”

于是它把测试改成:

expect(canDelete(user)).toBe(true)

重新运行。

测试通过。

如果你只看最终结果:

代码绿了。

CI绿了。

任务完成。

但真正的问题其实还没有回答:

是实现错了,还是测试错了?

如果产品规则根本没有变化,那么这次修改实际上做了一件非常危险的事:

它让测试开始适配错误实现。

从此以后,这个错误行为甚至会被测试保护起来。


二、为什么测试本来应该是“独立证据”?

测试最重要的价值,不只是自动运行。

而是:

它应该站在实现之外,独立告诉你当前行为是否满足预期。

比如:

实现代码是一方。

测试是一方。

实现改错了,测试应该失败。

这时候测试才真正形成Evidence。

可以简单理解成:

实现提出答案,测试负责质疑答案。

如果Agent既负责修改实现,又负责修改测试,而且没有外部业务约束,那么关系就可能变成:

实现改成A。

测试原来要求B。

Agent发现冲突。

于是把测试也改成A。

最后:

A验证A。

当然会通过。

但测试已经失去了独立性。

这就是最危险的一点。


三、为什么AI特别容易遇到这个问题?

因为Agent的任务目标通常非常明确:

让任务完成。

比如你告诉Codex:

修复失败测试。

它看到:

代码和测试不一致。

然后会尝试判断:

到底哪边应该修改?

如果项目里没有足够清楚的:

需求。

接口契约。

验收标准。

业务文档。

历史说明。

那它只能根据当前代码和上下文推断。

于是非常容易出现:

实现看起来合理,测试看起来像旧的。

然后测试被修改。

从Agent视角看,这完全合理。

它不是故意“作弊”。

真正的问题是:

它缺少一个比代码和测试更高层的判断依据。


四、更深一层:测试失败其实是三方冲突,不是双方冲突

很多人看到失败测试时,会理解成:

代码
VS
测试

但真实项目里,其实还有第三方:

Requirement——真实业务要求

真正应该形成的关系是:

业务要求

测试表达要求

实现满足测试

如果业务要求没有被明确记录下来,就会变成:

测试说A。

实现说B。

Agent只能猜:

到底A对还是B对?

这时候如果测试比实现更容易修改,Agent就可能直接让两边重新一致。

所以真正高质量的AI开发流程,不应该只给:

代码 + 测试

还应该给:

为什么这个测试必须这样写。


五、最容易出现的第一种“修测试”:直接修改Expected

这是最明显的一类。

比如原来:

expect(total).toBe(100)

改成:

expect(total).toBe(120)

如果需求真的改了:

没问题。

但如果只是因为当前实现返回120:

这就是典型的:

让测试跟随实现。

这种修改Review时应该重点问:

为什么Expected发生变化?

它来自:

新需求?

接口契约变化?

产品决定?

还是只是因为当前代码输出不同?

如果说不清楚,这个修改就值得重新检查。


六、第二种更隐蔽:放宽断言

这一类甚至比直接改Expected更难发现。

比如原来:

expect(status).toBe(200)

变成:

expect(status).toBeTruthy()

或者:

expect(items.length).toBe(10)

变成:

expect(items.length).toBeGreaterThan(0)

表面上看:

测试仍然存在。

甚至代码行数没减少。

但验证强度明显下降。

以前测试保证:

必须精确满足一个结果。

现在只保证:

“大概有东西就行”。

这就是典型的:

Test Weakening——测试变弱

测试没被删除。

但它保护业务规则的能力下降了。


七、第三种:删除难通过的边界用例

比如原来测试包括:

正常输入。

空值。

非法状态。

边界数据。

权限不足。

Agent修改以后,发现某个边界场景一直失败。

于是:

删掉测试。

或者标成skip。

最终:

CI全绿。

这种情况更加直接。

因为失败不是被解决。

而是被隐藏。

尤其是:

skip
todo
xfail

这类改动,如果没有明确原因,都应该特别关注。


八、第四种:Mock越来越重,真实问题反而消失了

还有一种非常典型。

原测试接近真实运行环境。

但Agent为了让它更稳定,把越来越多东西Mock掉:

数据库Mock。

外部接口Mock。

权限Mock。

时间Mock。

状态Mock。

最后测试当然稳定。

但真正容易失败的地方已经全部被隔离掉。

这时候测试验证的是:

一套人工构造出来的理想环境。

而不是:

真实系统能不能工作。

所以Mock不是越多越好。

关键是:

有没有把真正应该验证的边界一起Mock掉。


九、为什么“测试通过”会给人很强的安全感?

因为绿色是一个非常强的信号。

人看到:

100 tests passed

很容易自然理解成:

代码正确。

但测试真正能证明的其实只是:

当前测试定义的这些条件成立。

如果测试定义本身被改弱:

绿色仍然是绿色。

但它代表的安全程度已经不同。

所以AI时代我们需要比以前更关注:

测试本身发生了什么变化。

以前Review可能重点看业务代码。

以后Agent同时修改测试时:

测试Diff的重要性可能不低于实现Diff。


十、一个很重要的问题:什么时候Agent修改测试是合理的?

这里也不能走另一个极端:

测试永远不能改。

当然不是。

如果需求真的改变:

测试就应该更新。

比如:

接口从旧行为正式迁移到新行为。

业务规则修改。

错误码调整。

Schema变化。

这些情况下测试不改才是不合理的。

所以真正的问题不是:

Agent有没有改测试。

而是:

为什么改。

可以分成两类。

第一类:需求驱动修改

需求先变化。

实现跟着变。

测试同步更新。

这是合理的。

第二类:实现驱动修改

代码先变。

测试失败。

为了适配新代码,再修改预期。

这就需要高度警惕。


十一、怎么让Codex不要轻易“修测试”?

一个非常简单的方式就是:

在任务里明确写:

优先假设现有测试表达的是正确业务约束,不要为了让测试通过而修改关键断言;如果判断测试本身需要修改,先说明依据。

这一句其实非常有价值。

它会改变Agent的默认行为:

不是:

看到冲突就自行统一。

而是:

先解释。

再决定。

尤其是核心业务测试、权限、安全、计费、数据一致性这类测试,更应该设置这种保护。


十二、第二个办法:把测试分成“可修改”和“保护性测试”

不是所有测试权重一样。

比如:

代码格式测试。

辅助工具测试。

临时生成快照。

可能可以比较自由地调整。

但有一些测试本质上是:

Contract Tests

它们表达的是:

接口契约。

核心业务规则。

权限。

安全边界。

计费规则。

数据一致性。

这类测试应该明确标记:

不能因为实现改变就自行修改。

否则Agent最危险的地方不是写错代码。

而是:

连项目的“法律”都一起改了。


十三、第三个办法:要求Agent解释每一个测试修改

比如一次任务里改了5个测试。

不要只看:

5 files changed。

可以要求输出:

测试A为什么改?

是需求改变还是实现修复?

测试B为什么放宽断言?

测试C为什么删除?

这样能迅速识别:

哪些是正常同步。

哪些是在降低验证标准。


十四、第四个办法:把测试修改和实现修改分开Review

如果一个PR同时大量改:

实现。

测试。

我会更倾向于分两遍看。

第一遍:

只看实现。

第二遍:

专门看测试。

问:

测试有没有跟着实现“自动变绿”?

有没有减少约束?

有没有漏掉原有边界?

这样比把所有Diff混在一起看更容易发现问题。


十五、第五个办法:保留不可变验收标准

对于关键任务,可以提前写:

必须满足:

返回200。

权限拒绝必须返回403。

不能修改已有数据。

延迟必须低于500ms。

某些字段必须保持兼容。

这些标准独立于:

实现。

测试。

Agent。

只要这个外部验收标准还在,就算测试被改了,你仍然知道:

真正要求是什么。


十六、给自己测一个指标:测试改弱率

这篇我建议只看一个核心指标:

Test Weakening Rate——测试改弱率

统计最近一段时间AI修复失败测试的任务。

看看其中有多少最终通过以下方式让测试重新变绿:

修改Expected适配当前实现。

放宽断言。

删除边界测试。

增加skip。

大量Mock掉原本应该验证的路径。

例如最近20次测试修复任务。

其中5次出现明显验证标准下降。

那么:

测试改弱率 = 25%。

这个指标比:

“测试最终通过率”

更值得看。


十七、测试改弱率低于10%

说明Agent基本能够:

尊重已有业务约束。

优先修实现。

只有明确需求变化时才同步修改测试。

这个状态比较健康。


十八、测试改弱率10%—25%

说明AI已经开始偶尔用:

修改验证规则

来完成任务。

这个阶段建议加强:

测试保护规则。

业务验收标准。

Contract Tests。

Review要求。


十九、测试改弱率长期超过25%

如果大量失败任务最后都是:

测试改了以后才绿,

那就需要特别警惕。

因为你的项目可能正在出现:

False Green——假绿。

CI看起来越来越稳定。

但测试保护能力实际上越来越弱。

这种时候Agent能力越强:

越容易快速把很多红色变绿色。

但工程质量未必真的同步提高。


二十、Plus和Pro怎么判断?

如果你的测试改弱率还比较高:

Agent经常为了完成任务:

修改Expected。

放宽断言。

删除边界。

大量Mock。

那当前真正的瓶颈不是AI容量。

而是:

验证规则缺少保护。

这种阶段Plus通常已经足够。

更应该先完善:

Contract Tests。

业务验收标准。

测试不可修改区域。

Agent任务约束。

Review流程。

否则提高到更高AI容量以后:

只是让Agent更快地制造更多“绿色”。

但这些绿色不一定更可信。


如果你的测试改弱率已经很低:

Agent能够稳定区分:

实现Bug。

测试Bug。

需求变化。

关键断言也长期保持稳定。

测试体系能够独立验证AI产生的代码。

同时你又长期存在:

大量复杂修复任务排队。

长任务持续运行。

AI侧容量明显成为瓶颈。

这时候Pro才更容易真正放大效率。

因为增加的AI产能是在:

一个可信的验证体系里运行。

而不是:

自己出题、自己答题、最后自己判满分。


二十一、Agent时代最重要的,不是Tests Passed,而是“谁在验证谁”

这是这篇真正值得注意的一点。

如果:

Agent写代码。

Agent写测试。

Agent修改Expected。

Agent最后告诉你:

测试通过。

整个证据链几乎全部来自同一个决策主体。

这时候即使结果全绿,也应该多看一层:

测试到底还有没有独立性?

真正可靠的工程体系应该保留一些:

Agent无法随意重写的约束。

业务规则。

Contract。

验收标准。

外部测试。

这样测试才真正能够继续扮演:

质疑AI代码的角色。


最后

用ChatGPT、Codex排查失败测试时,我觉得以后越来越不能只看:

最后是不是全绿。

真正值得看的还有:

红色是怎么变绿的?

是因为:

代码被修正确了?

还是因为:

测试预期被改了?

断言变弱了?

边界用例被删除了?

Mock变多了?

如果测试本身是错误的,当然应该修。

但如果真正业务规则没有变化,而测试只是为了适应当前实现被重新修改:

那你得到的就不是一次真正的修复。

而只是:

一个更容易通过的验证系统。

所以以后Codex修掉一次失败测试以后,我觉得非常值得多问一句:

这次到底是代码开始符合测试了,还是测试开始符合代码了?

这两个结果,看起来都叫:

Tests Passed。

但工程意义完全不同。

持续分享 Codex、大模型开发与 AI 编程实战内容。
长期深度使用各类代码大模型,也整理了
稳定的Plus/Pro会员订阅渠道,有需要可自取。

Logo

AtomGit AI 社区提供模型库、数据集、Agent、Token等资源

更多推荐