单元测试生成 Prompt:14 个真能抓出 bug 的模板

别再让 AI'给这个文件写测试'。14 个可直接复制的单元测试 Prompt——边界值、错误路径、mock 与 fake、参数化、回归锁定,针对 Vitest 4 / Jest 30 / pytest 9 调过。

“给这个文件写单元测试”是最懒的测试生成 Prompt,结果一看就明白:5 条 happy path 断言,错误分支一条没有,不该 mock 的全 mock 了。好的单元测试 Prompt 必须点出边界(null / 空 / 最大 / 负数),禁止过度 mock,并要求至少一条回归测试锁住”行为”而不是”形状”。

速览(TL;DR)

  • 先单独跑一轮”边界”,再跑第二轮”happy path”。分两步走比一句”把测试都写了”能稳定抓出更多真 bug。
  • 强制 mock 纪律:只 mock 系统边界的 I/O(网络、文件系统、时钟、随机、数据库驱动);包内的一切用真值或内存 fake。
  • 每条回归测试都要绑定原文照搬的失败输入。AI 会编造根本复现不了 bug 的输入。
  • AI 写完的测试,永远先在旧代码上跑一次。如果在旧代码上也能过,那这条测试就写错了。
  • 这些模板与框架无关。下面的默认值针对 Vitest 4 / Jest 30(TS/JS)和 pytest 9(Python),换个 runner 名字其余照用。

适合哪些人

赶进度但需要真覆盖率的开发、强制测试纪律的 tech lead、给老代码补测试的工程师,以及用 Claude Code 或 Cursor agent 模式的独立开发者。在 2026 年 6 月的第三方编程评测里,Claude Code 的测试生成得分最高,Cursor 是最强的 AI 原生 IDE——但两者默认仍然过度 mock,而这正是这套 Prompt 要纠正的。

什么时候不建议这样写 Prompt

别给一次性脚本写测试——维护成本大于收益。也别在还没理解代码时让 AI 写测试,否则测试一变绿,你就把现有的 bug 当成”预期行为”固化下来了。

先选好你的测试框架(2026 年 6 月)

这些 Prompt 任何 runner 都能用,但版本会改动几个写法。生成前先核对一下现状:

框架当前版本适合写 Prompt 注意
Vitest4.1(2026-03)新 TS/JS 与 ESM 项目、Vite 应用v4 的 Browser Mode 已稳定;同一套测试下 watch 重跑通常比 Jest 快很多
Jest30(2025-06)老的 CommonJS、大型 monorepo、React NativeCJS 优先;ESM 仍在实验标志后。务必告诉 AI 你的模块体系
pytest9.1(2026-06)Python已越过 8.x;优先用 monkeypatch / pytest-mock,别处处用裸 unittest.mock

Vitest 和 Jest 的 matcher 和 mock 接口几乎一致,所以一条 Jest 的 Prompt 通常把 jest 换成 vi 就能在 Vitest 下跑。Prompt 里要明确点出 runner,AI 才会写对 import。

Prompt 结构公式

每个单元测试生成 Prompt 都要带这六个要素:

  • 角色:让 AI 扮演谁(senior 测试工程师 / QA Lead / staff 工程师)。
  • 上下文:文件、框架、运行时、函数签名、docstring。
  • 目标:一个具体可交付物——边界套件、happy path 套件、回归测试、漏洞清单。
  • 限制:AI 不能做什么(别过度 mock、别编造输入、别顺手写实现)。
  • 输出格式:可直接运行的测试文件、markdown 漏洞表,或 unified diff。
  • 示例 / 信号:从你自己仓库里挑一条写得好的测试贴进去,让输出贴合你的命名和断言风格。

最后这一条最关键。贴一条你仓库里真实的测试,比任何文字描述都更能把 AI 锚定到你的规范上。

这套 Prompt 适合用在哪

  • AI 写完的新模块,合并前补测试
  • 重构前给老函数补覆盖率
  • bug 修完后写回归测试锁住行为
  • 一组相似输入做参数化表驱动测试
  • toHaveBeenCalled 这种脆弱断言换成行为断言

14 个可直接复制的 Prompt 模板

[方括号] 里的是占位符——发送前换成你的真实值。模板正文保持英文,方便直接喂给模型。

1. 边界优先测试

永远先跑这条,再跑 happy path。

You are a senior test engineer. For the function [functionName] in [filePath], write Vitest tests that ONLY cover boundary and error inputs: null, undefined, empty string, empty array, max int, negative, NaN, very long string, malformed input. Do not write any happy-path test in this pass. Each test must have a clear "given / when / then" comment. Use describe.each for tables when inputs share a shape.

可替换变量: functionName 函数名, filePath 文件路径

优化建议: 追加:“If a boundary cannot be reached because the function signature forbids it, list it as an // unreachable: <reason> comment instead of inventing a test.”

2. happy path 测试(第二轮)

Now write happy-path Vitest tests for [functionName]. For each public behaviour described in its docstring or surrounding comments, write one test. Use real values, not generic strings like "test". Use it("returns X when Y") naming. Do not duplicate any boundary case already covered.

可替换变量: functionName

3. mock 纪律

Write tests for [functionName]. Mock ONLY: (a) network / fs / time / random, (b) external services. Do NOT mock: (a) pure functions in the same package, (b) data classes, (c) anything you could pass as a real value. If you reach for vi.mock (or jest.mock) on a same-package import, stop and use the real implementation instead. Note in a comment which dependencies you chose to mock and why.

可替换变量: functionName

优化建议: Python(pytest 9)把 mock 助手换成 monkeypatch / pytest-mock

4. bug 回归测试

I just fixed this bug: [bugDescription]. The failing input was: [failingInput]. Write a single regression test named "regression: <bug-title>" that fails on the old code (before fix) and passes on the new code. Add a comment with the bug ticket link and the exact behaviour the test locks.

可替换变量: bugDescription bug 描述, failingInput 原始失败输入

5. 参数化表驱动

Convert these example-based tests into a describe.each (Vitest / Jest) or pytest.mark.parametrize table. Group by behaviour. Keep one row per distinct equivalence class; don't list 10 rows that all hit the same branch. Name the rows so a failure tells you which case broke.

可替换变量: language:TS / Python / Go

6. 测行为而不是测实现

重构后内部 call site 变了,最适合用这条清理脆弱测试。

Refactor these tests so they assert OUTPUTS / SIDE EFFECTS, not internal calls. Remove any toHaveBeenCalledWith(internalHelper, ...) assertions. Replace with assertions on the function's return value or the externally observable side effect (DB row, emitted event, file written).

7. 异步 + 计时器

For the async function [functionName] that uses setTimeout / setInterval / Promise.race, write Vitest tests using vi.useFakeTimers() (or jest.useFakeTimers()). Cover: (1) resolves before timeout, (2) rejects on timeout, (3) cleanup on abort, (4) no dangling timers after await. Avoid real await new Promise(r => setTimeout(r, ...)); that's flake.

可替换变量: functionName

8. 错误形状契约测试

Write tests that pin the ERROR SHAPE of [functionName]. For each documented error path, assert: (a) the thrown class / error code, (b) the message contains a stable phrase callers can match, (c) cause is preserved if wrapping. Errors are part of the API; these tests prevent silent breaking changes.

可替换变量: functionName

9. 覆盖率漏洞识别

识别完再跑模板 1 或 2。

Read this test file and the function under test. Without running coverage, identify uncovered branches by inspection. Return a markdown table: branch (file:line) | reason it's missed | suggested test name. Don't write the tests yet; just list the gaps so I can prioritise.

10. fake 替代 mock

Replace these mocks with an in-memory FAKE. The fake should implement the same interface as the real dependency, store state, and let tests assert on that state. Mocks should remain only for I/O at the system boundary (network / DB driver). Show the fake class and 2 example tests that use it.

可替换变量: interface 接口名,如 UserRepository、EmailSender

11. 属性测试脚手架

For the pure function [functionName], write property-based tests using [framework]. Identify 3 invariants (e.g. idempotence, commutativity, round-trip), and for each, write one property. Use shrinking-friendly generators. Don't test concrete examples; those belong in unit tests.

可替换变量: functionName, framework:fast-check / hypothesis / proptest

12. 测试命名与组织

Reorganise these tests so each describe block represents one BEHAVIOUR of [functionName], and each it() reads as a sentence: it("returns 0 when input is empty"). Group setup with beforeEach only when 2 or more tests share it. Flag tests where the name doesn't match what the body actually asserts.

13. snapshot 策略

Audit these snapshot tests: (1) Which snapshots lock behaviour (good) vs random output / dates / IDs (bad)? (2) Replace bad snapshots with targeted assertions. (3) For React components, snapshot the rendered text + ARIA roles, not the full HTML tree. Output a diff plan.

14. spec → TDD

Here is the spec for [functionName]:

[specText]

Write the tests FIRST (TDD style), as a single Vitest file, before any implementation. Each test corresponds to one bullet in the spec. Mark unimplemented behaviour with it.todo. Do not write any implementation.

可替换变量: functionName, specText

容易踩的坑

  • 只说”写测试”、不点边界——你只会拿到 happy path。
  • 让 AI 全 mock——最后测的是 mock,不是代码。
  • snapshot 一切——脆,而且失败时啥都看不出来。
  • 让 AI 同一轮里同时写测试和断言,没有 ground truth——bug 当成预期固化下来。
  • 测试命名 test1/test2——失败时啥也定位不到。
  • 一次让生成 50 条——一堆重复;先边界、后 happy path。
  • 跳过错误路径——这恰恰是 AI 写的代码最常出错的地方。

进阶优化技巧

  • 先边界、再 happy path——分两个 Prompt,输出质量明显提升。
  • 写回归测试时把原始失败输入原文贴进去,别让 AI 编造。
  • 强制要求 // given / when / then 注释,逼 AI 先把意图想清楚,暴露不一致。
  • AI 写完测试先在旧代码上跑一次,能过就说明这条写错了。
  • 用真实 fixture 数据,别用 "foo",能暴露更多 bug。
  • React 测试要 ARIA role 和 text,不要 className 或 DOM 结构。
  • git diff 一起贴进去,AI 才会针对改动写测试,而不是整个文件。
  • 在 agent 工具(Claude Code、Cursor)里让 agent 自己跑套件并迭代——但断言要你亲自审,因为 AI 测试跑过只能说明它符合代码当前的行为。

FAQ

  • AI 写的测试能直接当覆盖率吗?: 不能,当草稿用。2026 年的第三方评测虽然给头部模型的测试生成打了高分,但它们写的断言仍然容易耦合到实现细节。新测试永远先在旧代码上跑一次——如果还能过,就说明根本没在测你以为的东西。
  • 到底要多少条才够?: 边界 + 错误路径 + 每个分支一条 happy path。增加测试不再增加 diff 覆盖率时就停。
  • 老代码该让 AI 直接补测试吗?: 必须先你自己读一遍。否则会把现有的 bug 当成预期行为锁住。
  • 为什么 AI 默认 mock 这么多?: 训练习惯。用模板 3、模板 10 强制改成 fake / 真值。
  • 怎么避免时间 / 随机 / 网络带来的 flaky?: 把这些当依赖注入,测试里用 fake 替换。模板 7 专门用 vi.useFakeTimers() / jest.useFakeTimers() 处理 timer。
  • 2026 年新测试该用 Vitest 还是 Jest?: 新 TS/JS 项目里 Vitest 4 已是常见默认——原生 ESM、watch 模式快得多。老的 CommonJS、大型 monorepo 或 React Native 继续用 Jest 30。两种情况上面的 Prompt 都适用,点明 runner 即可。
  • Python / Go 适用吗?: 适用。把 runner 换成 pytest 9 或 go test,先边界、fake 优先、回归锁定的结构完全一样。

相关阅读

标签: #Prompt #编程 #测试 #单元测试