文章指出,仅让 AI 根据实现生成测试,容易将现有缺陷固化为预期行为。已展示的改进包括先写清业务意图与约束,再要求模型列出可观察行为、用例表及疑似缺陷。
AI 编程工具几秒钟就能写出一个测试文件。大多数开发者也发现了其中的问题:测试全绿,覆盖率上去了,真正的 bug 却照样穿过测试,被发布出去。
这通常不是模型不擅长写测试,而是 prompt 没有交代那些真正让测试有价值的信息。现在,无论是在 Cursor、Claude Code、ChatGPT 还是 Copilot Chat 中,我都会对每一个测试 prompt 做下面五处调整。下面也会解释为什么每一处都重要。
如果输入只有实现代码,实现本身就会变成规格说明。模型读出代码做了什么,然后断言代码就应该这么做——连 bug 也一起写进断言。
用自己的话补上一两句,说明代码的预期行为:
What this code is supposed to do:
prorate() returns the charge for the unused part of a billing period,
rounded half-up to cents. It never returns a negative amount.
这样,模型就有了检验代码的依据。如果它发现代码可能返回负数,就有理由把问题指出来,而不是用测试把这个行为固定下来。
“写测试”很容易让模型一味堆数量。先让它列出行为清单和测试用例表:
Before writing any code:
1. List every observable behaviour (return values, errors, side effects, branches).
Mark anything that looks like a bug with [SUSPECT].
2. For each behaviour, list cases: happy path, boundaries (empty, zero, one, max,
off-by-one), invalid input, error paths.
Show me both. Then write the tests.
花十秒钟就能审阅一张用例表。如果缺少边界情况或错误路径,你马上就能看出来,不必等到翻完 300 行生成的代码才发现。
描述项目约定,比如“我们用 Vitest,factory 放在 tests/factories,测试命名方式是……”,效果往往不好。直接给它看示例,效果就很好:
Conventions to copy: tests/billing/invoice.test.ts
Match its imports, helpers, file location and naming.
这一行就能避免大部分“引入新的断言库、放进不同的文件夹、重复创建 fixture”的问题。
模型会选择让测试变绿的最短路径。把边界明确写出来:
Rules:
- Do not change production code to make tests pass. If the code seems wrong, report it.
- Expected values must be hand-computed literals, not the same formula as the code.
- Only stub true boundaries: clock, randomness, network, third-party APIs.
- No fixed sleeps. Freeze time and seed randomness explicitly.
光是第二条,就能消除整整一类“镜像测试”:这些测试从构造方式上就注定与实现一致,永远不会因为你关心的问题而失败。
最后加上一轮自检:
For each test, state in one line the specific bug it would catch.
Delete any test whose answer is "none" or "same as another test".
List behaviours from the case table that still have no test.
然后由你亲自做一遍验证:故意把代码改坏,比如把 < 改成 <=,或者让函数提前返回,看看有没有测试变红。如果一个都没有,这些测试就只是摆设。
上面这五处调整是基本骨架。具体流程应该随任务变化:
集成/API 测试: 按风险给场景排序——契约、校验、auth(包括访问另一个租户的资源)、持久化、幂等性、下游故障——并禁止 mock 数据库。
E2E: 区分值得断言的检查点与无关紧要的 UI 细节,使用 role/label locator,并明确要求审查测试的不稳定因素。
遗留代码: 在做任何修改之前,先编写 characterization tests,把当前行为固定下来,包括已有的 bug。
不稳定的测试: 要求给出按可能性排序的假设,并附上证据和复现步骤。重试和延长 sleep 都不算修复。
覆盖率: 把覆盖率百分比当作制定风险驱动测试计划的一项输入,而不是目标。
我使用的每一个好的测试 prompt,都包含同样的五个部分:
找一个你很熟悉的函数试一试。如果用例表发现了一个你之前没有写下来的边界情况,就说明这套方法起作用了。
我把自己使用的八种 prompt——单元测试、spec-first、集成/API、E2E、characterization、不稳定测试诊断、覆盖率规划和 mock 边界——连同两份检查清单、一条 Cursor rule 和一份 PDF 指南,打包成了 AI Test Generation Prompt Pack:https://lirenplr.gumroad.com/l/cuofwr。上面介绍的所有方法,都不需要购买这个包也能使用。
—— Pluto · @Mars_Peakul
如果想采取进一步措施,可以考虑屏蔽此人和/或举报滥用行为。