SuperAPI AI 工具集

tdd-anti-patterns

测试与质量

查看项目

更新于 10-16 16:28

技能介绍

用途

这是一份测试套件反模式参考目录,用于审计测试代码中的 TDD 坏味道(test smells)。每个坏味道都包含三部分:可观察的症状、为什么它是错误的解释,以及指向能够修复它的 TDD 技术的指引。

何时使用

  • 对测试代码进行代码评审。
  • 测试套件已经变得糟糕,需要有人判断优先修什么。
  • 新人入职:解释「我们这里不这么做,原因如下」。
  • 诊断 CI 痛苦(慢、易碎、难调试)。

何时不要使用

  • 你还没有写测试——应直接使用按技术划分的技能。
  • 某个具体测试因正当原因失败——那是调试,不是审计。

主要内容(反模式目录)

  • 测试相互依赖:单独运行能通过,一起运行则失败。症状是 CI 在「本地总是通过」的测试上失败。修复:tdd-test-isolation。
  • 套件过慢:单个测试超过约 100 毫秒,整个套件耗时数小时。症状是开发者不再在本地运行测试。修复:把真实 I/O、真实时间、真实网络推到替身之后(tdd-mock-object);单元测试使用内存假实现。
  • 易碎测试:同一测试有时失败有时通过,代码未变。症状是「重跑构建」文化。原因包括竞态条件、真实时钟、非确定性顺序、网络。修复:注入时钟、注入执行器、给随机性设定种子、隔离(tdd-test-isolation)。
  • 神秘访客:测试依赖它自己没放进去的数据——数据库某行、磁盘上某文件。症状是断言中出现 select * from users where id = 42,却没有记录 42 从何而来。修复:测试自己搭建 fixture。
  • 过度 mock:测试验证调用了哪些协作者,而不是产生了什么行为。症状是到处都是 verify(mock, times(2));一次无害重构会破坏十个测试。修复:只在边界处 mock(tdd-mock-object);领域内部使用真实对象。
  • 测试框架本身:例如验证 @Transactional 会回滚,或 Jackson 能序列化 LocalDate。症状是即使团队代码不存在,该测试也会通过。修复:删除它;框架已经测试了自己。
  • 测试实现而非行为:断言内部状态(私有字段值、方法调用次数)而不是可观察结果。症状是不改变行为的重构会破坏大量测试。修复:断言输出、返回值、对外部的副作用——而不是内部细节。
  • mock 你不拥有的东西:直接 mock 第三方 SDK 的类。症状是 SDK 升级悄悄改变 mock 行为;测试通过,生产环境出错。修复:把 SDK 包在你拥有的一层小接口后面;mock 该接口;为 SDK 添加 tdd-learning-test。
  • bug 修复没有测试:修复 bug 却没有回归测试。症状是 bug 数周后回归。修复:tdd-regression-test——每次 bug 修复都配一个测试。
  • 巨型测试:单个测试搭建 50 行 fixture、执行 10 种行为、断言 20 个结果。症状是失败时没人知道原因。修复:拆成小而专注的测试;默认每个测试一个断言。
  • 测试代码不经代码评审:测试代码被当作二等公民——不评审、不重构、不讲究命名。症状是测试套件比生产代码还乱。修复:像对待生产代码一样对待测试代码。测试套件是设计的可执行规格说明。
  • mock 你自己的数据类:本可以 Order(...) 却用 mock<Order>()。症状是 mock 需要被告知它拥有的每个属性。修复:构造真实值对象。
  • 断言藏在辅助方法里:测试调用 verifyEverything(...),真正的断言藏在三层之下。症状是失败信息只有 AssertionError,没有上下文。修复:断言属于测试方法;辅助方法应搭建状态,而不是断言。
  • 用随机 sleep 做同步:用 Thread.sleep(2000) 来「等待异步操作完成」。症状是又慢又易碎,两个问题都没真正解决。修复:使用正确的同步原语、等待 future,对挂起代码使用 kp-testing-coroutines 虚拟时间。
  • 没有断言消息:assertEquals(expected, actual) 缺少第三个参数消息。症状是 CI 失败只显示两个数字,看不出在检查什么。修复:添加断言消息或逐行描述。

决策启发式

  • 严重性随影响范围扩大:CI 中一个慢测试影响每个开发者;某个文件里一个易碎测试只是小修。
  • 反复重跑易碎测试直到通过不是修复。要找出非确定性的来源。
  • 「为了让测试更快」而添加的 mock,是最常见的过度 mock 起点。对每一个新 mock 都要质疑。
  • 测试即文档:测试的名称和形态应告诉你代码做什么。如果做不到,不论通过与否,这个测试都是坏的。

关于「捕捉反模式」的反模式

  • 把所有长测试都视为坏——有时集成测试确实合理地更长。
  • 要求零 mock——边界 mock 是正确的且必要的。
  • 「为整洁」重构测试却不重跑它们。测试代码也是代码,适用同样的纪律。

相关技能

  • 其余十一个 tdd-* 技能——每个修复都引用相关技术。
  • kp-testing-coroutines——协程特有的测试坏味道。
  • kp-anti-patterns——Kotlin 语言坏味道(也常出现在测试代码中)。

参考资料

  • Kent Beck,《Test-Driven Development By Example》第 27 章——许多测试反模式与模式一同被提及。
  • Gerard Meszaros,《xUnit Test Patterns》——详尽坏味道目录(权威参考)。
  • Robert C. Martin,《Clean Code》第 9 章——整洁测试;FIRST 原则(Fast、Independent、Repeatable、Self-validating、Timely)。

限制

本技能是参考目录而非自动化工具;没有明确给出具体的运行命令、安装步骤、收费与价格、支持平台等信息,这些内容暂未提供。