返回资讯中心
外部精选
软件工程
#开发者实时流

honest-tests 推出 CLI 与 agent skill,用变异测试识别 AI 编码工具生成的假测试

来源标题:Honest-tests – detect fake tests and prove them with mutation testing

honest-tests 是一个 agent skill 加 CLI 工具,用于阻止 Claude Code、Codex、Cursor 等 AI 编码工具编写假测试,并通过变异代码验证现有测试是否真的会失败。它提供 audit 命令找出无法失败的测试,以及 mutate 命令破坏代码后观察测试是否察觉。该工具以 npx @mintwork/honest-tests 方式调用,可检测自我比较、无断言、错误断言被 try/catch 吞掉、只测 mock 等假测试模式。

Hacker News 最新mintworks2026/11/14 · 今天22 分钟阅读

以下正文由 AI 翻译自 Hacker News 最新(原文版权归原站所有),可能存在偏差,重要内容请以原文为准。

honest-tests 是一个 agent skill + CLI,用来阻止 Claude Code、Codex、Cursor 等工具写出假测试——并通过变异你的代码、观察测试是否仍然保持绿色,来证明你现有测试中哪些是假的。 npx @mintwork/honest-tests audit . # 找出永远不会失败的测试 npx @mintwork/honest-tests mutate src/pricing.ts # 证明它:破坏代码,看谁会发现 问题所在 让 agent「加一些测试」,你会得到满屏的绿色对勾。凑近看看: it('handles multiple items', () => { const result = subtotal(items); expect(result).toBe(result); // 把一个值和它自己比较 }); it('applies a percentage discount', () => { applyDiscount(100, 10); // 完全没有断言 }); it('computes the discounted price', () => { try { expect(applyDiscount(100, 20)).toBe(70); // 错了(应该是 80)——但依然通过 } catch {} }); it('gives members free shipping', () => { const qualifies = vi.fn().mockReturnValue(true); expect(qualifies(10, true)).toBe(true); // 测试的是它刚刚配置的 mock }); 每一个都能通过。覆盖率上升了。什么都没被验证。 $ npx @mintwork/honest-tests audit test/pricing.test.ts test/pricing.test.ts 16:5 error 把 result 和它自己比较——这永远不会失败。 tautological-assertion │ expect(result).toBe(result); 25:1 error 测试 "applies a percentage discount" 没有任何断言——它只能因为崩溃而失败。 no-assertions │ it('applies a percentage discount', () => { 39:7 error 断言位于会吞掉错误的 try/catch 中——当它失败时,catch 吃掉了 AssertionError,测试照样通过。 swallowed-assertion │ expect(applyDiscount(100, 20)).toBe(70); // 错了!但 catch 把它藏起来了 53:5 error 对 mock qualifies 的返回值做断言(在第 52 行配置)——这验证的是 mock,不是你的代码。 asserts-on-mock │ expect(qualifies(10, true)).toBe(true); ... 还有 5 条 10 个测试中有 7 个不会失败(或者根本没有测试它们声称要测的东西)。 1 个文件,10 个测试:7 个错误,2 个警告。用 --verbose 查看修复建议。 然后是杀手级演示——内置的变异测试。honest-tests 会在你的源码中(临时副本上)翻转运算符、布尔值、常量和返回值,运行你的测试,并展示每一个没人注意到的改动: $ npx @mintwork/honest-tests mutate src/pricing.ts 22 个变异体存活——你的测试实际上没有检查这些行: src/pricing.ts:15 if (percent < 0 || percent > 100) { ✗ < → <= ✗ || → && ✗ > → >= ✗ 100 → 101 src/pricing.ts:22 return isMember || total >= 50; ✗ >= → > ✗ 50 → 51 ... 变异分数:15.4%(26 个中杀死 4 个,超时 0 个,存活 22 个;基线 0.5s) 之后:同样的代码,诚实的测试 it('takes the percentage off the amount', () => { expect(applyDiscount(200, 25)).toBe(150); }); it('accepts the 0% and 100% boundaries', () => { expect(applyDiscount(200, 0)).toBe(200); expect(applyDiscount(200, 100)).toBe(0); }); it('rejects percentages outside 0..100', () => { expect(() => applyDiscount(200, -1)).toThrow(RangeError); expect(() => applyDiscount(200, 101)).toThrow(RangeError); }); it('starts free shipping exactly at 50 for non-members', () => { expect(qualifiesForFreeShipping(49.99, false)).toBe(false); expect(qualifiesForFreeShipping(50, false)).toBe(true); }); $ npx @mintwork/honest-tests audit test && npx @mintwork/honest-tests mutate src/pricing.ts ✓ 2 个文件中的 12 个测试:没有发现假测试。 变异分数:100%(26 个中杀死 26 个,超时 0 个,存活 0 个;基线 0.5s) ✓ 每一个变异都被捕获了。这些测试确实在测试一些东西。 两个项目都放在 fixtures/ 下——你可以自己运行 npm run demo:bad / npm run demo:good (npm run demo:gif 会根据真实的 CLI 输出重新渲染上面的 GIF)。 Claude Code(plugin + skill) /plugin marketplace add mintwork/honest-tests /plugin install honest-tests@honest-tests 或者只把 skill 放进仓库:npx @mintwork/honest-tests init claude(写入 .claude/skills/honest-tests/)。 然后说:「为 src/cart.ts 写测试」或「审查我的测试」——或者使用斜杠命令 /honest-tests:audit [path] 和 /honest-tests:mutate <file>。 npx @mintwork/honest-tests init codex # 向 AGENTS.md 追加 honest-tests 段落(幂等) Cursor npx @mintwork/honest-tests init cursor # 写入 .cursor/rules/honest-tests.mdc(自动附加到测试文件) 任意 agent / CI npx @mintwork/honest-tests audit . --json # 机器可读的发现 npx @mintwork/honest-tests mutate src/x.ts --min-score 80 # 变异分数低于 80% 时让 CI 失败 这个 skill 如何工作 该 skill(Agent Skills 格式)教给 agent 一条规则——一个测试只有在它所声称的行为被破坏时失败,才算诚实——外加两个工作流: agent 按需加载的参考文档:测试坏味道目录、 编写诚实的测试、 审查流程、 变异测试。 规则 严重级别 捕获内容 no-assertions error 测试体从不做断言——除非崩溃,否则通过 tautological-assertion error expect(x).toBe(x)、expect(f(1)).toBe(f(1))、expect(true).toBe(true)、toHaveLength(list.length) asserts-on-mock error 对测试自己配置的 mock 的返回值做断言(vi.fn().mockReturnValue、spyOn、vi.mocked、.mock.results) asserts-on-own-literal error 对测试刚刚构造的字面量做断言:const p = { service: 's3' }; expect(p.service).toBe('s3')——常见于运行时的「类型测试」 never-failing-assertion error expect(x).toBeTruthy(从未被调用)、裸 expect(x)、未 await 的 .resolves/.rejects、typeof x 为「defined」、length >= 0、{} 为 truthy swallowed-assertion error 断言位于 try 中,而 catch 没有重新抛出 catch-only-assertion error 断言只在 catch 中——没有抛出任何东西时也通过 mocked-unit-under-test error 在 cart.test.ts 中写 vi.mock('./cart');对被测单元自身的导出进行 spy 并替换 focused-test error 留下了 .only、fit、fdescribe excessive-mocking warning 一个文件中 5 个以上的模块 mock snapshot-only warning 唯一的断言是 snapshot conditional-assertion warning 断言位于 if / && / 三元表达式之后 skipped-test warning .skip、xit、xdescribe

(原文较长,此处译出前 6000 字,完整内容请点「打开原文」。)

正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。

打开原文
去社区讨论 →