小项目实战 3:用 AI 设计测试用例并发现隐藏 Bug

开篇

上一篇文章,我们为 task-api 确定了接口契约和统一错误结构。

今天给这个项目补测试,但重点不是让 AI 凑出更多测试文件,而是让它回答一个更重要的问题:

哪些场景最可能让这组 API 出错?

本篇使用 Vitest + Supertest,测试对象是 Express 应用本身,不启动真实端口。流程如下:

text 复制代码
业务规则
  ↓
测试场景
  ↓
接口测试
  ↓
发现隐藏 Bug
  ↓
修复并回归

一、先让 AI 设计场景,不要直接生成代码

我们已经确定了任务 API 的基本规则:

  • title 必须是非空字符串。
  • 创建成功返回 201。
  • PATCH 只允许修改 completed。
  • 任务不存在返回 404 和 TASK_NOT_FOUND。
  • 删除成功返回 204。

把这些规则交给 AI:

text 复制代码
请根据下面的 task-api 接口契约设计测试场景,先不要生成测试代码。

接口:
- GET /api/tasks
- POST /api/tasks
- PATCH /api/tasks/:id
- DELETE /api/tasks/:id

规则:
- title 必须是非空字符串。
- POST 成功返回 201。
- PATCH 只允许修改 completed,且 completed 必须是布尔值。
- 任务不存在返回 404 和 TASK_NOT_FOUND。
- DELETE 成功返回 204 且没有响应体。

请按以下分类输出:
1. 正常流程。
2. 参数边界。
3. 资源不存在。
4. 非法字段和状态转换。
5. 数据副作用。

每个场景写清楚:请求、预期状态码、预期响应、需要验证的数据变化。
不要把"代码执行到了"当成测试目标。

先看场景表有两个好处:

  • 可以发现需求中还没有定义的行为。
  • 可以避免 AI 只测试正常请求和 null 参数。

二、测试场景应该覆盖什么

一组够用的最小场景如下:

分类 场景 重点断言
正常 创建并查询任务 状态码、字段和列表数据
参数 title 为空或不是字符串 返回 400 和 INVALID_TITLE
修改 completed 不是布尔值 返回 400 和 INVALID_STATUS
资源 修改或删除不存在的任务 返回 404
字段 PATCH 传入 id id 不被覆盖
删除 删除后再次查询 任务确实不存在
隔离 每个测试使用独立数据 测试之间互不影响

最后两项很容易被忽略。

如果测试只断言响应状态码,不检查数据是否真的改变,删除接口即使什么都没删也可能通过测试。

如果测试共用一份内存数据,前一个测试创建的任务可能影响后一个测试,测试结果就不可信。

三、让 AI 生成接口测试

场景确认后,再让 AI 写代码:

text 复制代码
请根据已确认的测试场景生成 task-api 的接口测试。

技术要求:
- 使用 Vitest 和 Supertest。
- 从 src/app.js 导入 app,不监听真实端口。
- 每个测试开始前重置内存数据。
- 每个测试只验证一个主要行为,但可以断言必要的副作用。
- 同时断言 HTTP 状态码、错误 code 和关键响应字段。
- 不要修改业务代码来迁就测试。

请先输出测试文件路径和测试分组,再输出完整代码。
最后说明每个测试对应哪条业务规则。

这里的"不要修改业务代码来迁就测试"很重要。

测试失败时,先判断是测试假设错了,还是业务代码真的有问题。不能为了让测试变绿,就把断言改成一个没有意义的宽松判断。

四、一段有价值的测试示例

以"PATCH 不能修改 id"为例:

javascript 复制代码
it("不允许通过 PATCH 修改任务 id", async () => {
  const created = await request(app)
    .post("/api/tasks")
    .send({ title: "原始任务" });

  const taskId = created.body.id;

  const response = await request(app)
    .patch(`/api/tasks/${taskId}`)
    .send({ id: 999, completed: true });

  expect(response.status).toBe(200);
  expect(response.body.id).toBe(taskId);
  expect(response.body.completed).toBe(true);
});

如果原代码直接把请求体合并到任务对象:

javascript 复制代码
Object.assign(task, req.body);

这个测试就可能失败,并暴露出一个隐藏 Bug:客户端可以覆盖服务端生成的 id。

正确做法是只读取允许修改的字段:

javascript 复制代码
task.completed = req.body.completed;

测试的价值就在这里:它不是重复实现代码,而是证明一个重要边界没有被破坏。

五、再补一个经常被忽略的删除测试

删除接口不能只断言 204:

javascript 复制代码
it("删除任务后,任务不再存在", async () => {
  const created = await request(app)
    .post("/api/tasks")
    .send({ title: "待删除任务" });

  const taskId = created.body.id;

  const deleted = await request(app)
    .delete(`/api/tasks/${taskId}`);

  expect(deleted.status).toBe(204);
  expect(deleted.text).toBe("");

  const query = await request(app).get(`/api/tasks/${taskId}`);
  expect(query.status).toBe(404);
  expect(query.body.error.code).toBe("TASK_NOT_FOUND");
});

如果项目当前没有 GET /api/tasks/:id,可以改为查询列表,再确认返回数组中不包含该任务。

测试应该适配已经确认的接口契约,不能为了示例偷偷增加接口。

六、如何判断 AI 生成的测试有没有价值

我会用下面 5 个问题审查测试:

text 复制代码
[ ] 测试是否来自明确的业务规则?
[ ] 失败时,能否说明具体行为不符合预期?
[ ] 是否检查了响应之外的数据变化?
[ ] 是否覆盖了空值、非法值、资源不存在等边界?
[ ] 测试之间是否相互独立?

还要警惕这几种"看起来很努力"的测试:

  • 只断言响应不为空。
  • 只断言函数被调用一次。
  • 只测试永远不会失败的固定输入。
  • 大量 Mock,却没有验证真实数据是否改变。
  • 为了覆盖率,给每一行代码机械地写测试。

测试数量多,不等于风险覆盖得好。

七、运行测试并记录结果

在 package.json 中配置:

json 复制代码
{
  "scripts": {
    "test": "vitest run"
  }
}

执行:

bash 复制代码
npm test

建议把失败结果记录成问题,而不是直接删掉失败用例:

text 复制代码
问题:PATCH 可以覆盖任务 id
触发:发送 { "id": 999, "completed": true }
影响:后续查询和删除可能操作错误资源
修复:只允许更新 completed 字段
回归:重新运行 PATCH 边界测试

这份记录也可以交给 AI,让它继续分析修复是否完整。

总结

用 AI 设计测试用例,推荐遵循下面的顺序:

text 复制代码
先给业务规则
  ↓
让 AI 列测试场景
  ↓
人工确认边界
  ↓
生成接口测试
  ↓
根据失败结果修复
  ↓
重新执行回归
  • 测试应该验证业务风险,而不是单纯追求覆盖率。
  • 正常流程、参数边界、资源不存在和数据副作用都要考虑。
  • 一个好的测试,应该能够发现真实 Bug。
  • 测试失败是反馈,不要为了变绿而削弱断言。

下一篇文章,我们将完成这个小项目的最后一轮工程检查:

《小项目实战 4:代码审查、重构与提交前检查》


✍坚持原创,求关注,点赞,收藏

相关推荐
想用offer打牌1 小时前
Personal Agent爆火 - 它到底是个什么
人工智能·后端·ai编程
zhangfeng11333 小时前
国产GPU/AI算力芯片现状(修订完整版 · 截至2026年9月
人工智能·算法·ai编程·npu
deepseek233 小时前
GPT-Synopsys 拆解:从调用 EDA 工具到成为专家用户,芯片设计 Agent 闭环的分工与红线
人工智能·openai·芯片设计
醒雷工程师4 小时前
用了AI辅助做项目的感想
ai编程
ZzT5 小时前
Claude Code Mods 是什么:给 Claude 加工具、在终端画界面
人工智能·ai编程·claude
明月_清风6 小时前
DuckDB:一个正在改变数据分析方式的数据库
数据库·数据分析·ai编程
deepseek237 小时前
OpenAI Dots 常驻智能体拆解:聊天免费、主动研究只读、委派才计费,动作分级才是本体
人工智能·openai·agent
abigalexy7 小时前
图解AI应用架构设计
人工智能·ai·架构·系统架构·aigc
垂钓的小鱼18 小时前
geo优化核心
aigc