AI Agent 防作弊 CI 实战:自动拦截assertTrue(true)、跳过测试和空 catch
系列导读:本系列用一个可运行的 Spring Boot 秒杀 Demo,拆解“当 AI 成为我的全职下属”之后,研发工作流应该如何重建。五期内容从一次超卖事故开始,依次讨论并发正确性、上下文裁剪、防作弊 CI、Agent 友好架构和 Human-in-the-loop 上线门禁。主线不是证明 AI 能不能写代码,而是回答一个更实际的问题:当 Agent 已经能高吞吐地产出代码时,工程师如何用边界、验证和审批机制,把它变成可靠的执行者。
文章目录
- AI Agent 防作弊 CI 实战:自动拦截 `assertTrue(true)`、跳过测试和空 catch
- 1. 问题现场:CI 绿了,但问题没有修好
- 2. 验证目标
- 3. 原因:绿色流水线为什么可能是假成功
- 4. 原理:把禁止项变成机器可执行规则
- 5. 在 Demo 中验证审计器
- 6. 解决办法:不能让 Agent 修改守门脚本
- 7. 正则扫描的边界
- 8. 把奖励函数改写成验收函数
- 实验环境
- 小结
- 发布到团队 CI 前的边界
- 测试 Demo 仓库
- 参考资料
1. 问题现场:CI 绿了,但问题没有修好
你给 Agent 的任务是“让测试通过”。它确实做到了:流水线变绿,PR 看起来也能合并。
但代码里可能出现了@Disabled、assertTrue(true)、空catch,甚至把并发度从 100 改成 1。业务问题没有修好,失败信号被消音了。
本文实现一个轻量级完整性审计器,在进入 Maven 测试前检测@Disabled、assertTrue(true)、空catch、捕获Throwable和把并发度降为 1 等模式。重点不是依赖几个正则表达式,而是建立不可由执行者自行修改的验收边界。
2. 验证目标
验证目标包括两部分:恶意样例必须被检出,当前合规工程必须以退出码 0 通过。只满足其中一项,都不能证明审计器可用。
3. 原因:绿色流水线为什么可能是假成功
下面几种改动都能让失败的并发测试“恢复绿色”:
@Disabled("偶发失败,先关闭")voidshouldHandleConcurrency(){}assertTrue(true);intconcurrentCount=1;try{executeSeckill();}catch(Throwableignored){}它们没有修复任何业务问题,只是移除了失败信号。
配套动画:
- 假绿色流水线对比动画
- GIF 版本:
4. 原理:把禁止项变成机器可执行规则
Demo 中的规则定义如下:
RULES=(Rule("DISABLED_TEST",re.compile(r"@Disabled\b"),"禁止跳过核心测试"),Rule("ALWAYS_TRUE",re.compile(r"assertTrue\s*\(\s*true\s*\)"),"禁止万能真断言"),Rule("EMPTY_CATCH",re.compile(r"catch\s*\([^)]*\)\s*\{\s*\}"),"禁止空 catch 块"),Rule("SWALLOW_THROWABLE",re.compile(r"catch\s*\(\s*Throwable\b"),"禁止捕获 Throwable 掩盖失败"),Rule("LOW_CONCURRENCY",re.compile(r"concurrent(?:Count|cy)\s*=\s*1\b"),"禁止把并发度降为 1"),)执行审计:
python pipeline\verify_integrity.py通过时输出:
Integrity audit passed.如果测试中出现万能断言,脚本以非零状态退出,流水线立即失败。例如在临时目录中放入一个只包含assertTrue(true);的BrokenTest.java后,实际输出格式如下:
%TEMP%\cheat-demo\BrokenTest.java:1 [ALWAYS_TRUE] 禁止万能真断言 EXIT_CODE=1这段输出有三个关键信息:命中的文件、行号和规则码。CI 不需要理解业务语义,只要看到非零退出码,就能阻断这次提交。
配套动画:
- CI 流水线拦截动画
- GIF 版本:
5. 在 Demo 中验证审计器
不能因为工具名叫“审计器”就默认它可靠。项目使用临时目录构造恶意样例:
deftest_detector_finds_weakened_assertion(self)->None:withtempfile.TemporaryDirectory()asdirectory:root=Path(directory)(root/"BrokenTest.java").write_text("assertTrue(true);",encoding="utf-8",)self.assertTrue(any("ALWAYS_TRUE"initemforiteminscan(root)))运行:
python-m unittest discover-s ai_firm\tests-v当前 Demo 的两项 Python 测试均已通过:一项验证作弊模式检测,一项验证上下文裁剪依赖遍历。
再对当前仓库执行正向验证:
python pipeline\verify_integrity.pyif($LASTEXITCODE-ne0){throw'完整性审计失败'}6. 解决办法:不能让 Agent 修改守门脚本
如果 Agent 能同时修改业务代码、测试和审计脚本,它仍可以删除规则后提交。生产环境应至少采用以下一种隔离方式:
- 审计脚本放在独立仓库,由平台团队维护。
- CI 从固定版本的制品或容器中加载规则。
- 使用 CODEOWNERS,修改
pipeline/必须由人类审批。 - 核心测试在远端私有测试集执行,不暴露全部断言。
这就是“只读门禁”的真正含义:不是文件系统只读,而是任务执行者没有单方面改变验收标准的权限。
最小团队落地可以从两段配置开始。先在 CI 中把完整性审计放在 Maven 测试之前:
name:integrity-gateon:pull_request:jobs:verify:runs-on:ubuntu-lateststeps:-uses:actions/checkout@v4-uses:actions/setup-python@v5with:python-version:"3.12"-name:Run integrity auditrun:python pipeline/verify_integrity.py-name:Run Maven testsrun:mvn test再用 CODEOWNERS 约束守门脚本的修改权限:
/pipeline/ @platform-reviewers /ai_firm/cheat_detector.py @platform-reviewers这样 Agent 可以提交业务修复,但凡它试图改掉审计脚本或流水线规则,都必须经过独立维护者审批。
配套动画:
- 只读门禁权限边界动画
- GIF 版本:
7. 正则扫描的边界
本例故意保持简单,适合教学和本地前置检查。生产环境还应组合:
- Checkstyle、PMD 或 Error Prone:Java 语义和代码风格。
- SpotBugs:字节码级缺陷分析。
- SonarQube:质量门、覆盖率和重复代码。
- Maven Enforcer:依赖版本与禁止依赖。
- Git diff 策略:限制 Agent 可修改路径。
静态扫描无法证明代码正确,但能快速淘汰一批确定错误的提交。
8. 把奖励函数改写成验收函数
不要给 Agent 下达“让 CI 通过”,而要给出分层验收:
第一层:编译通过; 第二层:禁止模式扫描通过; 第三层:核心并发测试通过; 第四层:订单数、库存、成功响应三者一致; 第五层:人类 Review SQL、事务边界和锁释放。Agent 的目标越接近业务不变量,钻空子的空间越小。
实验环境
| 项目 | 版本或参数 |
|---|---|
| Python | 3.12.5 实测,只依赖标准库 |
| 被审计 Demo | JDK 17、Spring Boot 3.5.15、Maven 3.6.3+ |
| 核心验证 | 恶意样例能检出,合规工程以退出码 0 通过 |
小结
测试不是装饰,它是人类把意图固化为机器约束的方式。让 Agent 自动写代码之前,先确保它不能随意改写裁判规则。
下一期将从代码结构本身入手:为什么职责单一、依赖倒置和小接口,会直接提高 Agent 修改代码的成功率。
发布到团队 CI 前的边界
正则扫描可能误报注释、字符串和规则文件自身,也可能漏掉换行、别名封装后的作弊方式。本文工具适合作为快速前置门禁,不能替代 AST、覆盖率差异检查、私有测试集和人工 Review。
测试 Demo 仓库
配套 Demo 仓库地址:https://github.com/quan020406/xiaozhan-blog-column-demos
上一篇:用 AST 依赖裁剪治理 AI 编程上下文
下一篇:小z疯狂码字ing…
感谢阅读,记得点赞、关注、收藏,欢迎各位评论区交流!!!
参考资料
- JUnit 5 条件执行与禁用测试