学习笔记-谈谈我对Test Double的理解,Mock,Fake,Stub等
前言
为了追求工程卓越,我最近开始学习 TDD(测试驱动开发)。因此,不可避免地会接触到 Test Double 这个概念。作为一个非英语母语者,刚看到这个词时,我真的以为它是指“测试两次”。随着理解的加深,我逐渐了解了 Dummy、Fake、Stub 等各种 Test Double 术语。我相信你和我一样,在最初接触这些概念时也会感到困惑。为了总结我的学习成果,我根据自己的理解整理了各种 Test Double 的定义及其用途。
各种 Test Double
由于不同语言对 Test Double 的实现和 API 提供方式各不相同,这里不会提供代码示例,而是仅提供业务场景的例子。
Dummy
Dummy 代表“从不被使用”,它仅用于填充参数列表,就像是一块美味的小零食,只是为了满足口感,但没有实际作用。例如,在测试一个方法时,传递的某个参数对测试内容没有影响,我们通常会使用 null
或者一个无意义的值来快速填充。
Fake
Fake 可以是基于内存实现的 Data Access Object(数据访问对象)或 Repository(存储库)。这种实现并不会真正操作数据库,而是使用一个简单的 HashMap 或某种专门用于测试的内存数据库来存储数据。这样可以快速原型化一个系统,并基于内存存储运行整个系统,而数据库设计相关的决策可以暂缓。例如,我们通常会使用 Fake 来确保测试环境中的支付请求始终返回成功结果。
Stub
Stub 代表的是包含预定义数据的对象,在测试时返回给调用者。它通常用于不希望返回真实数据或不想引发其他副作用的场景。例如,当某个对象需要从数据库中获取数据时,我们不需要实际与数据库交互,也不像 Fake 那样从内存获取数据,而是直接返回预定义的数据。
Spy
Spy 是经过特殊处理的 Stub,除了具备 Stub 的功能外,还会记录方法调用时的一些信息。它就像一个情报间谍,在函数调用过程中偷偷记录你想知道的信息,并以某种方式做出反应。例如,当我们想要知道某个方法在执行过程中发送了多少封邮件时,就可以使用 Spy 来记录邮件发送的次数。
Mock
Mock 是仅包含调用信息和预定义行为的对象,我们可以使用 verify(mockInstanceName)
来验证特定行为(方法)是否被调用。Mock 主要用于替代真实对象,适用于不希望实际执行生产代码或者难以在测试中验证真实代码执行情况的场景。例如,在测试邮件服务时,我们不希望每次运行测试都发送一封真实邮件,因为这很难验证邮件是否真正被发送或接收。而在这种情况下,我们关心的是邮件服务在业务流程中是否被正确调用,因此可以使用 Mock 来完全模拟邮件服务的行为,定义不同调用情况下的方法返回值,从而模拟一个看似真实的邮件服务。
现实场景映射
在阅读了大量关于 Test Double 的文章后,我发现大多数讲解都晦涩难懂。忽然灵光一闪,我想到一个自己的例子,希望能帮助大家更好地理解这些概念。(如果有错误,欢迎指正。)
首先,我们设定一个场景:我们的工厂需要制造一根定制化的管道,它的功能是监测流经的沙水,并在检测到沙水时自动打开过滤器,将沙水过滤成清水(请试着代入这个场景)。基于此,作为工厂的质检员,我们当然需要在管道出厂前进行一系列测试,最终给它贴上合格标签。为此,我们进行了如下测试来帮助我们完成这项任务。
- 将其放入整个或部分管道系统,通水(或各种其他液体)测试其是否能正常工作(集成测试)。
- 通过各种已经制作好的测试模具,即“Test Double”(比如测试管道的机器、喷水机、喷沙水机、识别水质的设备等)来测试其功能(单元测试)。
- 入口连接到一个 Fake 喷水器或喷沙机,观察是否能产出清水。
- 出口模拟输出液体(Stub),看是否能顺利进水(虽然有些牵强)。
- 当沙水通过管道时,管道内部的过滤器扣会自动打开,此时过滤装置会脱落。我向管道输入沙水,希望观察过滤器扣是否打开(Mock)。此时我不关心过滤器是否真正存在,只想验证过滤器扣的行为是否发生。
- 为了监测过滤器是否能在一小时内连续打开十次,我在外部加了一个计数器,用于记录其随时间变化的打开次数(Spy)。
- 每根管道在出厂前必须贴上标签(即使没有标签也可以使用,但这是一项强制规定),所以我们先贴上一个无关的标签(Dummy)。