mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
2065 words
5 minutes
AI 说‘修好了’,我该如何验证
2026-05-28

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. 暂时不要修改实现。

对应的失败测试可能是:

@Test
void 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 --short
git diff --stat
git diff -- src/main/java/com/example/order/export/CsvOrderWriter.java
git diff -- src/test/java/com/example/order/export/CsvOrderWriterTest.java

检查内容包括:

  • 是否只修改了允许范围内的文件;
  • 失败测试的断言是否被放宽;
  • 是否删除了原有测试;
  • 是否引入与问题无关的重构;
  • 是否加入新依赖;
  • 是否把真实数据、密钥或本机路径写进代码。

尤其要警惕这种“修复”:

assertThat(csv).contains("\"张三,测试账号\"");
assertThat(csv).contains("张三,测试账号");

测试确实会通过,但产品问题没有解决。AI 优化的是通过率,而不是用户体验。

5. 做一次红—绿验证#

修复后的目标测试应该变绿:

mvn -Dtest=CsvOrderWriterTest#shouldEscapeUserNameContainingComma test

但如果条件允许,我还会确认这个测试真的能抓住旧问题:

  1. 保留新增测试;
  2. 临时撤回实现修改;
  3. 运行测试,确认失败;
  4. 恢复实现;
  5. 再运行测试,确认通过。

这就是最直接的回归测试证据:

旧实现 + 新测试 = 失败
新实现 + 新测试 = 通过

如果撤回修复后测试仍然通过,那么这个测试并没有保护我们关心的行为。

6. 不要只测试用户报告的一个输入#

逗号问题修复后,至少补充同一规则的其他边界:

@Test
void 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 状态码是否为 200
Content-Type 是否为 text/csv
Content-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 生成代码的速度才能真正转化为工程效率。

Share

If this article helped you, please share it with others!

AI 说‘修好了’,我该如何验证
https://mizuki.mysqil.com/posts/ai-verification/
Author
梦幻晨风
Published at
2026-05-28
License
CC BY-NC-SA 4.0

Some information may be outdated

Table of Contents