在做程序测试时,常会用到测试替身来协助我们快速完成测试。
有时候被测试系统(system under test(SUT))很难测试,因为在测试环境下依赖的组件不能正常使用。如外部系统。
当在一个不能使用真实依赖组件(depended-on component(DOC))的地方写test时,我们可以使用Test Double[1]。
Test Double
Test Double概括起来,有以下几种:
Martin Fowler在Mocks Aren't Stubs [2]中给出解释:
Martin Fowler解释的还不是太明白,我又收集了更明白一点的解释[3]:
•Dummy - just bogus values to stisfy the API
Example: If you're testing a method of a class which requires many mandatory parameters in a constructor which have no effect on your test, then you may create dummy objects for the purpose of creating new instances of a class.
•Fake - create a test implementation of a class which may have a dependency on some external infrastructure. (It's good practice that your unit test does NOT actually interact with external infrastructure.)
Example: Create fake implementation for accessing a database, replace it with in-memory collection.
•Stub - override methods to return hard-coded values,also refered to as state-based
Example:Your test class depends on a method calculate() taking 5 minutes to complete.Rather than warit for 5 minutes you can replace its real implementation with stub that returns hard-coded values;taking only a small faction of the time.
•Mock - very similar to Stub But interaction-based rather than state-based.This means you don't expect from Mock to return some value,but to assume that specific order of method calls are made.
Example:You're testing a user registration class.After calling save,it should call send ConfirmationEmail.
上面的解释已经很明白了,简单总结一下:
Dummy:主要是用来填充参数,以构造需要测试的对象
Fake:简单模拟实现,如Dao,但只是空实现
Stub:相对fake多了点硬编码返回值,两者很相似
Mock:相对状态验证更多的是行为验证
阅读到这儿大概率已经明白了,也就fake与stub还有点模糊,要想明白更清晰的区别,需要先了解一下生命周期和验证方式:
1、生命周期
每个测试都是由四个依次执行的阶段:
初始化(SetUp)、执行测试(Exercise)、验证结果(Verify)和复原(Teardown)
2、验证方式
在验证阶段,通常有两种验证方式:状态验证与行为验证
状态验证:
代码语言:javascript复制//肯定会使用的assert
assertEquals(expected,actual);
行为验证:
代码语言:javascript复制//Mockito中的verify
verify(mock, times(3)).do();
徐昊老师有个生动的描述:
万恶淫为首,论迹不论心,论心世上少完人;不关心过程,只看结果。结果验证
百善孝为先,论心不论迹,论迹贫家无孝子;看重过程,不看重结果。行为推断
所以我们判断淫棍总比判断孝子准确
测试策略是要保证有效性的同时,尽可能降低测试成本。
fake、stub、spy、mock 以此排序,成本越来越低,同时有效性也越来越低。
spy与mock在使用Mockito时能明显感受到它们俩的区别,fake与stub的区别,从上面的定义看,很接近。
徐昊老师引入进程的视角来进一步区分:
跨进程边界是fake,进程之间的stub就是fake。
结合上面的解释明确多了,当使用数据库时,fake一个内存数据库。
以进程划分:
进程间替身:dummy fake spy
进程内替身:stub mock spy
以验证方式划分:
严格来说,我们状态验证使用fake stub,行为验证使用mock。
dummy和spy都不验证,dummy是不能验,spy是还没验。
spy可以按状态(比如回放)也可以按行为(比如对比)验证。
spy已经在行为验证和状态验证的边上。spy是记录调用,对调用加上验证就是mock
如果用记录来reply就是录播测试,比如你在两个系统间做了spy,把请求和结果播放出来,这样相当于用spy的数据做了stub。
把请求结果和目标结果做对比,实际相当于拿spy数据做了mock
spy本身只取数据不验证,但是正常的doc内部数据不可知,因而spy是一种替身技术,并不是验证技术。
spy is dumb mock
总结
测试策略是要保证有效性的同时,尽可能降低测试成本。因此Test Double是SUT中测试中不可或缺的,Test Double的形式有dummy fake stub spy mock,以进程维度与验证方式维度能更好地区分它们。
References
[1]
Test Double: http://xunitpatterns.com/Test Double.html
[2]
Mocks Aren't Stubs : https://martinfowler.com/articles/mocksArentStubs.html
[3]
收集了更明白一点的解释: https://code-examples.net/en/q/34c8d7