< BACK 我为什么又开始写测试了(AI 让我这样做)-- 线条艺术插图

我重新开始写测试的原因(AI逼的)

回到2022年初,我做了个安静的决定:我停止为大多数通过Seahawk的WordPress和Node项目编写单元测试。没有声张。没有博客文章。我就是...停止了。理由看起来很充分,我们每月发布15到20个客户网站,我有三个其他开发者轮换,我写的测试看起来像没人读的文档。手动QA发现了真正的bug。测试就是做戏。

快进到2023年底。GitHub Copilot在我的编辑器里已经有大约八个月了。我也开始在一旁对任何全新项目使用Cursor。速度确实非常出色。但事情开始发生了。bug出现在我没有触碰的地方。看起来正确的逻辑在我从未想过检查的边界情况中出了问题。最糟糕的是,AI完全没意识到它出错了。它用同样的自信缩进写出了破碎的代码。

就是那时我重新拿起了测试。

---

我放弃测试的时期(以及为什么当时有意义)

老实说?对于某些特定项目,跳过测试是正确的决定。如果你在WordPress中构建一个五页的宣传册网站,为联系表单插件编写PHPUnit测试纯粹是浪费时间。我坚持这个立场。

Seahawk很长一段时间以来的核心业务正是那种工作——高容量、相对低复杂度、范围明确。客户给你一个Figma文件,你建设,你QA,你发布。反馈循环很短。如果有什么破坏了,你会在几个小时内知道。在那种背景下写测试,相当于开发者给便签纸贴膜。

但我太激进地推广了那个经验。我开始把所有项目都当成宣传网站对待。即使是那些有自定义WooCommerce结账流的项目。即使是我们在2023年初为法兰克福的一个客户构建的金融科技仪表板,完全自定义REST API、JWT认证、三个不同的用户权限级别。没有测试。只是"小心的手动QA"。那很傲慢,也给我们教训了。

法兰克福项目上线时存在一个权限漏洞,让编辑级用户在特定的过滤条件组合下能够查询管理员级的数据。我们直到他们的内部团队在上线后六周进行安全审计才发现。很尴尬。虽然可以修复。但这种问题本来可以被基础集成测试在我们提交拉取请求前就标出来。

---

AI编码工具实际上改变了什么

人们在谈论Copilot、Cursor或任何本月最火的模型时,往往忽略的是:代码看起来是对的。这就是问题所在。

当初级开发者写出有bug的代码时,你通常能看到其中的不确定性。奇怪的变量名、一条注释说"// not sure about this"、一个明显被复制粘贴两次的函数。代码会暴露自己的脆弱性。AI代码不会。它在风格上是一致的,命名得当,结构看起来很有意图。自信完全是表面现象。

斯坦福大学人机交互小组的研究表明,使用AI助手的开发者往往在第一眼就过度信任生成的代码。这与我的实际经历相符。我会扫一眼Copilot写的40行函数,想"嗯,基本就是我会写的样子",然后继续。有时候没问题。有时候它在不知不觉中误解了我的真实需求。

我一直遇到的特定失败模式是:围绕边界情况的条件逻辑,这是AI没有理由预期的。它会写一个在正常路径上完美运行的函数,然后在null输入、空数组或非标准日期格式上悄悄失败。这些是我自己写代码时三十秒就能想到的问题,因为我会边写边思考。

速度陷阱

这里存在一个真实的生产力陷阱。AI 让你变快。快速感觉像是好的。你开始更快地发布,你开始更不仔细地审查,因为速度感觉像是质量的证据。但它不是。当你在提示语言模型时,速度和正确性没有相关性。

去年九月,我在一个客户项目中投入了大约 40% 更多的功能,这是没有 AI 助手我无法做到的。这个项目在上线后也出现了比我在两年内发布的任何东西都更多的错误。不是灾难性的错误。但很烦人。那种会侵蚀客户信任的错误。

---

为什么测试现在的工作方式不同(AI 参与其中)

当我回到测试时,我没有回到旧的工作流。先写测试,再写实现,然后AI辅助的代码审查,这是我现在定居的循环。

有趣的是,AI 在编写测试方面实际上非常出色,在编写应用程序逻辑时并不总是这样出色。给 Copilot 一个定义良好的函数签名,要求它生成测试套件,它会产生边界情况覆盖,我手动编写需要花费二十分钟。当任务具体是"找出这可能如何破坏"时,它对不顺利的路径想象得很好。

所以我有点颠倒了这个东西。我编写测试规范。AI 填充测试用例。然后 AI 编写实现。然后我通过这些测试的角度来阅读实现,而不仅仅是直接阅读代码。

这比纯粹的凭直觉编码要慢。但它比旧的手动编写所有内容(包括测试)的工作流程更快。自法兰克福以来,它没有发布过任何权限错误。

我实际在使用的工具

  • [Vitest](https://vitest.dev) 用于任何JavaScript或TypeScript。去年完全替代了Jest,配置更清晰,监视模式也很快。
  • PHPUnit 仍然是首选,用于 WordPress 和自定义 PHP 工作。没有什么能替代它。
  • Cursor的"测试这个函数"快捷方式,真的是我用过的任何编辑器中最有用的单一功能之一。
  • GitHub Actions 用于 CI。每次推送到 main 时都会运行测试。大多数项目花费约 90 秒。

---

反对测试的论点(理性阐述)

我想认真对待这个观点,因为我曾持有这个立场近两年。

真正的论证不是"测试没用"。而是"测试有成本,很多项目不值得那个成本"。编写和维护测试套件需要时间。在一个生命周期短的项目上,一个活动微网站,一个营销落地页,一个黑客松原型,那个时间投入零回报。项目在测试拯救你之前就死了。

还有一个更微妙的问题:糟糕的测试比没有测试更糟糕。一个测试套件通过是因为测试本身就是同义反复(你基本上是在测试你的函数是否返回了你告诉它返回的东西),这会给你虚假的信心。我在代理机构看过这种情况。开发者写的测试总是通过,因为没有人质疑他们实际在验证什么。

Martin Fowler写得很好,覆盖率百分比不是测试质量的衡量。90%覆盖率可以掩盖一个完全空心的套件。

所以:不要测试所有东西。不要因为感觉专业就测试。测试是因为你已经识别出了承重的逻辑,而破坏它会很昂贵。

---

我现在测试的内容(以及我不测试的)

这是我在过去八九个月里得出的实际决定:

我测试:

  1. 任何处理金钱、权限或数据转换的函数
  2. 任何不是直接 CRUD 传递的 API 端点
  3. 客户在书面上指定了确切行为的自定义业务逻辑
  4. 任何我没有逐行完整阅读的 AI 生成代码

我不测试:

  • UI 渲染(快照测试在我的九年职业生涯中从未救过我一次。一次都没有。)
  • 第三方 API 包装器,其中外部行为不在我的控制范围内
  • 运行一次就被删除的一次性脚本
  • 标准的 WordPress hooks,除非它们做的是不同寻常的事情

就这样。没有宏大的哲学。只是一个基于我踩过的坑的列表。

---

实际上对我有效的工作流程

由于一些在 Slack 社区中认识的人提过问题,这是我的实际流程:

  1. 在文件顶部写一段简短的规格注释,说明这个模块做什么、不做什么、以及我已经知道的边界情况。
  2. 在编写任何实现之前,让 Cursor 根据那个注释生成测试用例。
  3. 检查那些测试用例。删除愚蠢的。添加AI遗漏的任何用例。
  4. 让Copilot或Cursor写实现代码。
  5. 运行测试。它们会失败。修复实现(不是测试)。
  6. 推送前读一遍差异,AI辅助生成的代码仍然需要人工审查。

第6步是不可协商的。过去四个月我仅通过在推送前慢速阅读diff就发现了三个真实存在的严重bug。没什么聪明的。就是阅读。

Kent Beck 最初对 TDD 的框架并不是关于 100% 的覆盖率或完美的方法论。它是关于构建一个足够快的反馈循环,在错误复合之前捕捉它们。这个想法——快速反馈循环——现在比 2003 年更相关。因为 AI 犯错的速度比我聘用过的任何开发者都快。

---

FAQ

这会减慢你的交付速度吗?

在复杂项目上大约提高 10 到 15%。在简单项目上,可能根本没有提升,AI 生成测试的速度如此之快,以至于开销最小。对于那些在发布后修复 bug 会花真金白银的项目(大多数涉及真金白银的项目都符合这个条件),那 15% 的提升值得一百倍的投入。

TypeScript呢?强类型不是替代了很多测试吗?

部分是的。TypeScript在编译时捕获了一大类错误,这些错误你过去需要通过测试才能发现。但类型不会测试业务逻辑。它们不会验证你的折扣计算函数是否为批发客户应用了正确的规则。这部分还是你的责任。

初级开发者在不写测试的情况下应该使用AI编码工具吗?

不应该。我的看法很明确。初级开发者使用Copilot但不写测试,本质上就像在自动驾驶模式下飞飞机,却不理解自动驾驶怎么工作,也不知道如何手动着陆。AI会生成看起来很高级的代码,初级开发者不知道哪些部分不可信,最后你会遇到生产事故。至少测试给了他们一种机制来验证他们接受的输出。

说实话,你为什么一开始就不测试了?

部分原因是倦怠。还有一个时期,每个项目都真的很简单,测试也真的发挥不了作用。错误的做法是没有注意到项目复杂度何时改变,也没有相应地调整。真正的教训不是"总是测试"或"永远不要测试",而是知道一个给定的项目属于哪个类别。

---

写测试过去感觉不像保护。它感觉像是文书工作。AI 改变了这一点。不是因为 AI 不好——它让我的速度明显更快——而是因为它引入了一类新的、充满信心、格式良好、看起来合理的错误,我用过去读代码的方式无法捕捉。测试不是为 AI 写的。它们是为我写的。在我接受模型交给我的任何东西之前,强制我思考我实际上需要代码做什么。

我希望两年前就这样框架化了。

相关阅读:我作为仍在交付代码的创始人如何每天使用 Claude Code、工具和 SEO。

< BACK