SuperAPI AI 工具集

tdd-mock-object

测试与质量

查看项目

更新于 10-16 16:28

技能介绍

用途

tdd-mock-object 用于指导开发者在测试中用测试替身(test double)替代真实协作对象。当测试否则会触及数据库、网络、文件系统、消息代理等“JVM 之外”的资源,或依赖系统时间、随机性等非确定性来源,或调用缓慢/收费/有速率限制的第三方 API 时,就适合使用测试替身。此外,当需要验证某个协作对象被正确调用(例如通知器被触发)时也可使用。

文档同时强调:应“不情愿地”使用它。过度 mock 会让测试耦合于实现而非行为。

何时不用

  • 依赖快速、确定且进程内——直接用真实对象。
  • mock 一个自己拥有的类,而其行为正是被测对象——那是在测试 mock,而非代码。
  • mock 值对象或数据结构——过度;直接构造真实对象。
  • 到处 mock,测试大部分是 mock 设置——先简化设计。

五种测试替身

采用 Meszaros 的分类:

  • Dummy:在真实值不被使用处传入的占位符(null、Object()、Kotlin 的 Any())。
  • Fake:可工作但简化的实现,如用 MutableMap<Id, Entity> 保存数据的内存 Repository。
  • Stub:返回预设答案,不验证调用方式。例如“当被问 findById(42) 时返回预设的 User”。
  • Spy:一种记录调用方式的 stub,以便测试对调用进行断言。
  • Mock:在测试运行前设置预期,若预期调用未发生则测试失败。相关工具库:Mockito、MockK、jMock。

日常交流中人们把五者统称“mock”;严谨讨论时区分有意义:stub 关注输入,mock 关注输出验证。

库选择(JVM)

  • Mockito:Java 标准。
  • MockK:Kotlin 惯用,支持 final 类和协程。
  • jMock:较老,严格。

Kotlin 项目选 MockK,Java 用 Mockito。

最大风险:过度 mock

测试 mock 所有协作对象会带来两个问题:

  1. 测试实现而非行为。重构改变调用的协作对象就会失败,即便外部可观察行为不变。
  2. 测试大部分是 mock 设置,读起来比生产代码还难。

边界与契约测试

遵循 Beck 的“不要 mock 你不拥有的东西”规则:mock 边界——你的代码与外部世界的接口。不要 mock 外部世界本身。边界是你的,可以一致地 fake;外部世界是他们的,你的 mock 会偏离真实行为且直到生产才被发现。

若为单元测试 fake 了 PaymentGateway,还应写契约测试,对 fake 与真实网关同时运行,断言它们在关键用例上行为一致。没有契约测试,fake 会漂移并说谎。

决策启发式

  • 默认保持克制。真实对象 > fake > stub > spy > mock,使用切实可行的最高保真度。
  • 在边界处 mock。域内部优先真实对象。
  • 时间:注入 Clock。随机:注入 Random。HTTP:注入你拥有的小接口,而非直接用第三方客户端。
  • 抵制验证“调用次数”的诱惑。verify(mock, times(3)) 几乎总是在测试实现而非行为。
  • 如果测试的 mock 设置行数多于断言行数,设计就在呼唤改变。

反模式

  • mock 数据库:常见但几乎总是错的。改用内存实现(H2、带真实数据库的 Testcontainers,或 fake repository)。
  • mock 值对象/数据类:用 data class Money(BigDecimal, Currency) 构造真实对象,不要 mock<Money>()。
  • mock 框架:测试 Spring 的 @Transactional 是否被调用,是在测试 Spring 而非你的代码。
  • 脆弱的调用次数验证:verify(repo, times(2)) 在无害重构后失败。
  • fake 没有契约测试:fake 行为偏离真实,测试通过,生产崩溃。
  • mock 你不拥有的东西(直接用第三方 SDK):把其 SDK 包在你拥有的小接口后面再 mock 那个接口。

相关技能

  • tdd-test-isolation:mock 是实现隔离的一种技术。
  • tdd-anti-patterns:过度 mock 与脆弱验证是目录中的坏味道。
  • kp-testing-coroutines:TestDispatcher 是类似 mock 的 dispatcher 替代;适用同样的纪律。
  • gof-kotlin-idioms:Kotlin 的 MockK 支持 final 类与挂起函数。

参考

  • Kent Beck, Test-Driven Development By Example, 第 27 章——“Mock Object”条目及 Self Shunt 变体。
  • Gerard Meszaros, xUnit Test Patterns——详尽的测试替身分类。
  • Steve Freeman & Nat Pryce, Growing Object-Oriented Software, Guided by Tests——“mock roles, not objects”——正确实践的经典指导。