ChatGPT、Codex排障:单个测试都能通过,为什么整套测试一跑就随机失败?
最近用ChatGPT、Codex排查测试问题时,有一类故障特别容易把人带偏:
单个测试拿出来跑,全部通过。
但一跑整套测试:
突然失败。
更麻烦的是:
再跑一次,失败的可能还不是同一个测试。
例如:
test_create_order PASS
test_cancel_order PASS
test_update_inventory PASS
单独执行全部正常。
但执行:
pytest
却变成:
test_update_inventory FAIL
再跑一次:
test_cancel_order FAIL
这时候很多人的第一反应是:
测试本身不稳定。
或者让Codex继续:
重跑测试。
加Retry。
调Timeout。
甚至直接修改失败断言。
但真正的问题可能根本不在这个“失败的测试”里。
而在:
Test Isolation——测试隔离
更准确一点说:
某个测试改变了其他测试运行时所依赖的环境。
所以才会出现:
单独都对。
放在一起却错。
一、为什么单个测试通过,不代表整个测试集没问题?
一个测试单独执行时,它通常面对的是:
相对干净的环境。
比如:
数据库初始状态固定。
全局变量还是默认值。
Mock还没被其他测试改过。
缓存为空。
临时文件不存在。
所以测试当然可以正常通过。
但整套测试执行时:
前一个测试留下的状态,可能会影响后一个测试。
例如测试A执行了:
user.role = "admin"
但执行结束以后没有恢复。
测试B原本假设:
user.role = "guest"
那测试B单独跑:
通过。
跟在测试A后面跑:
失败。
于是表面上看:
test_b有问题。
实际上真正污染环境的是:
test_a。
这就是测试污染最麻烦的地方。
报错位置和根因位置经常不是同一个地方。
二、最常见的第一类污染:数据库状态没有清理
比如测试A创建了一条订单:
order_id = 1001
测试结束以后,这条数据仍然留在测试数据库里。
测试B开始时假设:
订单表为空。
于是执行:
assert order_count == 0
直接失败。
但如果只运行测试B:
数据库刚好是干净的。
它自然又会通过。
这类问题非常常见于:
共享测试数据库。
Fixture清理不完整。
Transaction没有Rollback。
测试异常退出以后跳过Teardown。
尤其是Agent自动补测试时,很容易只考虑:
怎么创建测试数据。
却没有同时考虑:
测试结束后怎么恢复环境。
三、第二类污染:全局变量和Singleton状态
比如项目里存在:
config.retry_count = 3
测试A为了验证特殊情况改成:
config.retry_count = 10
但结束以后没有恢复。
测试B继续运行时:
拿到的已经不是默认配置。
这种问题在:
全局配置。
Singleton。
内存缓存。
Feature Flag。
静态变量。
里尤其常见。
最麻烦的是:
代码逻辑本身完全没错。
真正错的是:
测试之间偷偷共享了状态。
四、第三类污染:Mock没有恢复
假设测试A为了模拟支付失败:
mock(payment_service).return_error()
如果这个Mock在测试结束后没有Reset,
测试B再调用支付服务时:
它仍然得到失败结果。
于是你看到的是:
test_payment_success FAIL
Codex如果只分析当前测试,很可能会怀疑:
业务代码。
接口逻辑。
甚至测试断言。
但真实问题却发生在前一个测试里:
Mock生命周期管理失败。
所以看到“单独跑通过、一起跑失败”时,
Mock恢复一定要优先检查。
五、第四类污染:缓存、文件和环境变量
还有一些问题更隐蔽。
比如测试A写入Redis:
user:123 = old_value
结束以后没有清理。
测试B开始以后直接命中缓存。
它根本没有走自己预期的数据库路径。
或者测试A生成:
/tmp/result.json
测试B看到文件已经存在,于是走了另一条逻辑。
甚至测试A修改:
ENV_MODE=test_a
后面的测试继续继承这个环境变量。
所以测试污染并不只发生在数据库。
任何跨测试共享的资源都有可能造成问题:
数据库。
Redis。
全局变量。
Mock。
文件。
环境变量。
端口。
时间。
随机数。
六、为什么失败看起来会“随机”?
因为测试执行顺序不一定永远一样。
尤其当项目使用:
并行测试。
随机顺序。
分组执行。
不同CI Worker。
以后,同一个测试前面跑的是谁可能不断变化。
假设:
测试B只有在测试A之后才失败。
那执行顺序:
A → B
就会失败。
但:
B → A
可能完全正常。
于是你看到的表象就是:
测试偶发失败。
但它其实并不随机。
真正隐藏的条件是:
Execution Order——执行顺序
只不过这个条件没有被你显式看到。
七、ChatGPT、Codex最容易犯的错误:一直盯着失败测试改
比如:
test_update_inventory FAILED
Agent很容易开始:
分析inventory逻辑。
修改断言。
增加等待。
Retry。
放宽条件。
但如果这个测试单独运行一直稳定通过:
第一反应其实应该改变。
不要先问:
“为什么这个测试失败?”
而应该先问:
“它前面运行了什么?”
这两个问题完全不同。
前一个把注意力放在失败点。
后一个开始寻找:
污染源。
这通常才是正确方向。
八、真正有效的定位方法:改变测试执行顺序
遇到这种问题,第一步不要改代码。
先做:
Shuffle Test Order——随机化测试顺序
把整套测试随机执行多次。
如果失败测试不断变化:
测试隔离问题的概率就很高。
然后进一步缩小范围。
比如发现:
只有:
test_a
test_b
一起运行时:
test_b失败。
那就分别尝试:
test_b
以及:
test_a test_b
如果结果是:
test_b PASS
test_a test_b FAIL
那基本已经非常明确:
test_a污染了test_b依赖的状态。
这种二分缩小范围的方法,比反复读失败测试高效很多。
九、第二步:建立“测试前后状态Diff”
如果怀疑数据库:
测试A执行前后分别记录:
表行数。
关键记录。
事务状态。
如果怀疑Redis:
记录相关Cache Key。
如果怀疑全局变量:
记录配置值。
如果怀疑Mock:
检查Mock是否恢复。
思路其实很简单:
Before Test A
↓
Run Test A
↓
After Test A
↓
State Diff
真正要找的是:
测试A结束以后,留下了什么本来不应该留下的东西。
这就是Evidence。
比直接修改测试靠谱得多。
十、第三步:让测试拥有真正独立的Fixture
一个稳定测试最好满足:
自己准备输入。
自己创建资源。
自己执行。
自己验证。
自己清理。
不要依赖:
“前面那个测试应该已经创建过用户。”
也不要假设:
“数据库现在应该是空的。”
例如不要写:
user_id = 1
如果这个ID依赖测试执行历史。
更可靠的是:
每个测试自己创建独立用户。
或者每个测试使用:
独立数据库事务。
独立Schema。
独立临时目录。
独立Cache Namespace。
测试环境越隔离:
执行顺序越不重要。
十一、时间和随机数也是隐藏污染源
有些测试没有共享数据库,也没有全局变量。
但仍然偶发失败。
这时候要检查:
时间和随机数。
例如:
now()
或者:
random()
如果测试依赖真实时间:
跨秒。
跨分钟。
时区。
夏令时。
都可能改变结果。
如果使用随机数据但没有固定Seed:
某些随机输入可能刚好触发边界条件。
这类问题虽然严格来说不完全是“测试之间污染”,
但表现非常相似:
单独跑正常。
整套执行偶发失败。
所以排查时也要确认:
时间是否可控。
Random Seed是否固定。
十二、并行测试会进一步放大测试污染
项目规模变大以后,测试经常会并行运行。
这时候原本很难出现的问题会突然暴露。
比如两个测试同时使用:
同一条用户记录。
同一个Redis Key。
同一个临时文件。
同一个端口。
同一个Mock Server。
于是原本串行时:
全部通过。
开启并行以后:
大量随机失败。
所以如果问题只出现在CI,而本地很难复现:
一定要比较:
CI是不是开启了Parallel Test。
Worker数量是多少。
资源是不是共享。
很多所谓“CI不稳定”,本质上其实是:
测试隔离不足,只是以前被串行执行掩盖了。
十三、怎么让Codex更快定位这类问题?
不要只把最后一个Error丢给它。
更有效的是给它这些信息:
单独运行:PASS
整套运行:FAIL
失败测试每次可能不同
CI开启并行
然后明确要求它优先检查:
共享状态。
Fixture生命周期。
数据库清理。
Mock恢复。
Cache Key。
临时文件。
执行顺序依赖。
这样Agent的排查方向会完全不同。
因为它知道:
现在找的不是普通业务Bug。
而是:
Test Isolation Failure
十四、给自己测一个指标:测试污染率
这篇我建议只看一个核心指标:
Test Pollution Rate——测试污染率
可以做一个很简单的测试:
将整套测试随机调整执行顺序100次。
统计其中有多少次因为:
共享状态。
顺序依赖。
环境残留。
导致本来单独通过的测试失败。
例如:
100轮随机执行里有7轮失败。
那么:
测试污染率 = 7%。
这个数字比:
“测试偶尔不稳定”
有意义得多。
十五、测试污染率高,应该先解决什么?
如果测试污染率比较高:
先看:
数据库有没有Reset。
Fixture是否真正独立。
Mock有没有Restore。
缓存是否清理。
临时文件是否隔离。
时间与随机数是否固定。
并行测试资源是否冲突。
最重要的一点是:
不要靠Retry掩盖问题。
因为重跑以后成功,只能证明:
当前顺序没有再次触发污染。
并不能证明问题消失了。
十六、怎么降低测试污染率?
最有效的方向其实就四个。
第一,每个测试自己准备数据
不要依赖其他测试留下的状态。
第二,每个测试结束后恢复环境
数据库Rollback。
Mock Restore。
Cache Clear。
临时资源清理。
第三,让共享资源命名隔离
比如:
独立Cache Prefix。
独立临时目录。
独立测试用户。
第四,把测试顺序随机化放进CI
不要一直用固定顺序。
随机顺序越早暴露问题,
越不容易等到项目变大以后再集中爆发。
十七、Plus和Pro怎么判断?
如果你的测试污染率还比较高:
单个测试经常通过。
整套运行却随机失败。
CI反复Retry。
Agent大量时间都在:
重新跑测试。
猜失败原因。
修改本来没有问题的测试。
那当前真正限制效率的不是ChatGPT、Codex容量。
而是:
测试体系本身不够稳定。
这种阶段Plus通常已经够用。
更值得先解决:
Fixture隔离。
共享状态。
Mock恢复。
缓存清理。
并发测试资源冲突。
否则增加更多AI容量:
只是让Agent更快地反复处理同一批随机失败。
如果你的测试污染率已经长期接近0:
测试执行顺序基本不影响结果。
并行执行也稳定。
失败能够稳定复现。
Agent可以根据可靠测试结果继续修改。
同时又长期存在:
大量复杂工程任务排队。
多个Agent持续处理项目。
AI容量真正开始成为瓶颈。
这时候Pro才更容易放大真实效率。
因为Agent依赖的是:
一个可信的测试反馈系统。
最后
单个测试都通过,整套测试却随机失败,
最容易让人产生一个错觉:
“这个测试不稳定。”
但很多时候真正不稳定的不是测试本身。
而是:
测试之间的边界。
一个测试留下数据库数据。
另一个留下Mock。
第三个修改全局配置。
第四个写入缓存。
单独执行时:
每一个都完全正确。
放在一起以后:
它们开始互相改变对方的运行环境。
所以遇到这种问题时,不要第一时间让ChatGPT、Codex去“修失败的测试”。
先问一句:
这个测试单独跑为什么能通过,它前面到底发生了什么?
很多随机失败的根因,就藏在这两个测试之间。
持续分享 Codex、大模型开发与 AI 编程实战内容。
长期深度使用各类代码大模型,也整理了稳定的Plus/Pro会员订阅渠道,有需要可自取。
更多推荐




所有评论(0)