AI 说“修好了”,我该如何验证
使用 AI 编程时,我们经常会看到这样的总结:
问题已经修复。我补充了异常处理和单元测试,所有测试均已通过。这段话听起来很让人安心,但它本身不是证据。
AI 可能只运行了一个测试,也可能没有运行命令;可能修复了表面现象,却破坏了其他路径;甚至可能为了让测试通过,直接修改了测试断言。
所以我会把 AI 的完成总结理解为:
它认为任务可能完成了,现在轮到我们验证。
这篇文章以订单 CSV 导出的乱码和转义问题为例,整理一套可以重复使用的验证流程。
1. “代码改了”和“问题修了”不是一回事
假设用户反馈:
订单导出后,用户名包含逗号时,Excel 里的列会错位。AI 修改了 CSV Writer:
private String escape(String value) { return "\"" + value + "\"";}它可能宣布问题已经解决,因为包含逗号的字段现在被双引号包住了。
但新的实现仍然有问题:
- 字段自身包含双引号时没有转义;
null会触发异常;- 所有字段都加引号,可能改变既有输出;
- 换行符是否正确处理还没有证明。
修改代码只能证明“文件发生了变化”。修复问题则需要证明:
原来的问题能够稳定复现 ↓修改后原问题消失 ↓相关正常行为没有被破坏2. 第一步永远是复现
不要先让 AI 猜原因。先把问题变成稳定输入和明确结果。
输入用户名:张三,测试账号
预期:该用户名完整出现在“用户名称”一列。
实际:逗号被识别为分隔符,“测试账号”进入下一列。然后让 AI 只做分析:
请先复现订单 CSV 在用户名包含逗号时列错位的问题。
要求:1. 找到负责生成 CSV 的代码;2. 写一个最小失败测试;3. 运行测试并提供失败信息;4. 暂时不要修改实现。对应的失败测试可能是:
@Testvoid shouldEscapeUserNameContainingComma() throws IOException { OrderExportRow row = new OrderExportRow( "ORD-001", LocalDateTime.of(2026, 5, 10, 12, 30), "张三,测试账号", OrderStatus.PAID, new BigDecimal("19.90") );
ByteArrayOutputStream output = new ByteArrayOutputStream(); csvOrderWriter.write(List.of(row), output);
String csv = output.toString(StandardCharsets.UTF_8); assertThat(csv).contains("\"张三,测试账号\"");}运行:
mvn -Dtest=CsvOrderWriterTest#shouldEscapeUserNameContainingComma test这一阶段需要看到测试确实失败。如果新增测试一开始就是绿色,可能说明:
- 测试没有覆盖用户报告的输入;
- 断言过于宽松;
- 当前分支已经包含修复;
- 问题出现在下载、编码或 Excel 解析,而不是 Writer。
没有复现,就无法确认后面的修改修复了同一个问题。
3. 用失败证据缩小根因
有了失败测试后,再让 AI 分析:
测试 shouldEscapeUserNameContainingComma 已稳定失败。实际输出中用户名逗号没有被 CSV 规则转义。
请分析根因,只修改 CsvOrderWriter 的字段转义逻辑。不要修改测试、字段顺序、响应头或其他类。这里给出了四个重要约束:
- 已经确认的失败现象;
- 根因所在的大致范围;
- 允许修改的文件;
- 明确禁止改变测试。
合理的转义方法类似:
static String escape(String value) { String safeValue = value == null ? "" : value; boolean needsQuotes = safeValue.contains(",") || safeValue.contains("\"") || safeValue.contains("\r") || safeValue.contains("\n");
if (!needsQuotes) { return safeValue; }
return "\"" + safeValue.replace("\"", "\"\"") + "\"";}但看到一段“看起来正确”的实现,还不能停止验证。
4. 检查 Diff,而不是只看总结
AI 完成修改后,先查看状态:
git status --shortgit diff --statgit diff -- src/main/java/com/example/order/export/CsvOrderWriter.javagit diff -- src/test/java/com/example/order/export/CsvOrderWriterTest.java检查内容包括:
- 是否只修改了允许范围内的文件;
- 失败测试的断言是否被放宽;
- 是否删除了原有测试;
- 是否引入与问题无关的重构;
- 是否加入新依赖;
- 是否把真实数据、密钥或本机路径写进代码。
尤其要警惕这种“修复”:
assertThat(csv).contains("\"张三,测试账号\"");assertThat(csv).contains("张三,测试账号");测试确实会通过,但产品问题没有解决。AI 优化的是通过率,而不是用户体验。
5. 做一次红—绿验证
修复后的目标测试应该变绿:
mvn -Dtest=CsvOrderWriterTest#shouldEscapeUserNameContainingComma test但如果条件允许,我还会确认这个测试真的能抓住旧问题:
- 保留新增测试;
- 临时撤回实现修改;
- 运行测试,确认失败;
- 恢复实现;
- 再运行测试,确认通过。
这就是最直接的回归测试证据:
旧实现 + 新测试 = 失败新实现 + 新测试 = 通过如果撤回修复后测试仍然通过,那么这个测试并没有保护我们关心的行为。
6. 不要只测试用户报告的一个输入
逗号问题修复后,至少补充同一规则的其他边界:
@Testvoid shouldFollowCsvEscapingRules() { assertThat(CsvOrderWriter.escape("张三")) .isEqualTo("张三"); assertThat(CsvOrderWriter.escape("张三,测试")) .isEqualTo("\"张三,测试\""); assertThat(CsvOrderWriter.escape("张三\"测试")) .isEqualTo("\"张三\"\"测试\""); assertThat(CsvOrderWriter.escape("张三\n测试")) .isEqualTo("\"张三\n测试\""); assertThat(CsvOrderWriter.escape(null)) .isEqualTo("");}实际项目中也可以拆成多个具名测试,让失败信息更清楚。
边界测试要来源于规则,而不是让 AI 随机凑数量:
| 规则 | 需要覆盖的输入 |
|---|---|
| 逗号会分隔列 | 包含逗号 |
| 双引号需要重复 | 包含一个或多个双引号 |
| 换行不能产生新记录 | \r、\n、\r\n |
| 空值需要稳定输出 | null、空字符串 |
| 字符编码不能丢失 | 中文、日文或特殊符号 |
7. 按验证层级逐步扩大范围
我通常不会修改一行代码后直接运行所有测试。更高效的方式是从小到大验证:
第一层:原始失败测试
mvn -Dtest=CsvOrderWriterTest#shouldEscapeUserNameContainingComma test证明用户报告的场景得到修复。
第二层:相关测试类
mvn -Dtest=CsvOrderWriterTest test证明 CSV Writer 的其他行为没有被破坏。
第三层:相关模块
mvn -pl order-service test证明订单模块仍然正常。
第四层:完整测试
mvn test检查跨模块回归。
第五层:构建交付物
mvn package证明项目可以产生最终制品。
验证范围逐步扩大,既能快速获得反馈,也能在失败时更容易定位是哪一层出现问题。
8. 测试通过不等于运行正常
有些问题不会被单元测试覆盖。例如:
- 响应头中的文件名在浏览器里乱码;
- 安全配置拦截了新接口;
- 生产配置缺少必要环境变量;
- 数据库查询在真实数据量下很慢;
- CSV 在常用表格软件中打开后编码异常。
因此还要根据变更类型做运行验证。对于订单导出,可以启动应用后请求接口:
curl -v \ -H "Authorization: Bearer $TOKEN" \ "http://localhost:8080/api/orders/export?startDate=2026-05-01&endDate=2026-05-31" \ --output orders.csv然后检查:
HTTP 状态码是否为 200Content-Type 是否为 text/csvContent-Disposition 是否为 attachment文件开头是否包含 UTF-8 BOM特殊用户名是否仍在同一列无权限用户是否被拒绝对于性能问题,还需要用接近真实的数据量验证查询耗时和内存占用。
9. AI 没有运行命令时要明确说明
有时执行环境缺少 Maven、数据库或必要配置。正确的完成总结应该是:
已修改 CsvOrderWriter,并新增回归测试。
已验证:- 静态检查通过。
未验证:- 未运行 Maven 测试,因为当前环境没有 JDK 21;- 未启动应用验证下载接口;- 未使用 Excel 打开生成文件。而不是:
修复已经完成,应该没有问题。“未验证”不是失败,它只是事实。把事实说清楚,下一位开发者才能继续完成验证。
10. 常见的虚假完成信号
只运行了新增测试
新增测试通过,只能证明局部行为。它不能证明旧功能没有回归。
编译通过
编译只能发现语法和类型问题,发现不了权限遗漏、字段顺序错误和业务边界。
测试数量增加
十个没有关键断言的测试,不如一个能稳定复现问题的回归测试。
AI 解释得很完整
解释质量和代码正确性没有必然关系。越流畅的总结,越容易让人跳过实际检查。
修改范围很小
小 Diff 更容易 Review,但一行权限条件写错仍然可能造成严重数据泄露。
11. 让 AI 输出证据,而不是结论
我更喜欢这样的任务结束格式:
完成后请输出:
1. 实际修改的文件;2. 每个文件修改的原因;3. 实际运行的完整命令;4. 每条命令的退出状态和测试数量;5. 没有覆盖的场景;6. 仍需人工确认的内容;7. 不要只写“测试通过”或“问题已修复”。AI 的报告可以帮助我们快速定位证据,但仍需要亲自查看命令输出和 Diff。
一个合格的验证报告类似:
## 修改
- CsvOrderWriter.java:按照 CSV 规则转义逗号、引号和换行。- CsvOrderWriterTest.java:增加原问题和相关边界测试。
## 已运行
- mvn -Dtest=CsvOrderWriterTest test:8 tests, 0 failures。- mvn test:126 tests, 0 failures。- mvn package:exit code 0。
## 人工验证
- 使用 curl 下载文件,响应头和 BOM 正确。- 使用表格软件打开,特殊用户名没有发生列错位。
## 未覆盖
- 尚未进行 10,000 行导出的性能测试。12. 我的验证清单
每次 AI 表示任务完成后,我会检查:
- 原问题能够稳定复现;
- 新增了会在旧实现上失败的回归测试;
- Git Diff 只包含必要修改;
- 测试没有被删除或放宽;
- 原始失败测试已经通过;
- 相关模块测试已经通过;
- 完整测试和构建已经通过;
- 根据变更类型完成了运行验证;
- 未验证项被明确记录;
- 权限、性能和数据安全经过人工判断。
总结
AI 说“修好了”只代表它结束了当前一轮工作,不代表软件已经满足需求。
可靠的完成标准应该建立在证据上:
可复现的问题+ 能抓住问题的测试+ 范围清晰的 Diff+ 逐层通过的验证命令+ 必要的人工检查= 可以相信的修复不要测试 AI 是否有信心,要测试系统是否表现正确。只要坚持从复现、回归测试、Diff、完整构建到运行验证的流程,AI 生成代码的速度才能真正转化为工程效率。
If this article helped you, please share it with others!
Some information may be outdated






