- 文章来源:itsCoder 的 WeeklyBolg 项目
- itsCoder主页:http://itscoder.com/
- 作者:JasonThink
- 审阅者:@Melo
以前我在内功之自动化测试中说到测试在项目中的重要性。单元测试是一个个「点」(细胞)的重构,是重构的基石,今天我们说单元测试中如何使用 Mock 及 Mockito 的。
Mock 概念
所谓的 Mock 就是创建一个类的虚假的对象,在测试环境中,用来替换掉真实的对象,主要提供两大功能:
- 验证这个对象的某些方法的调用情况,调用了多少次,参数是什么等等
- 指定这个对象的某些方法的行为,返回特定的值,或者是执行特定的动作
要使用 Mock,一般需要用到 Mock 框架,这篇文章我们使用 Mockito 这个框架,这个是Java界使用最广泛的一个mock框架。
在 Gradle 添加 Mockito 依赖
|
|
如何使用?
我们首先看一下官方的例子:
一般使用 Mockito 需要执行下面三步:
- 模拟并替换测试代码中外部依赖。
- 执行测试代码
- 验证测试代码是否被正确的执行
创建 Mock 对象的方式:
- mock(toMockObject.class)
- 注解的方式 @Mock,注意要利用注解, 首先要告诉 Mockito 框架, 可以
@Rule public MockitoRule mockitoRule = MockitoJUnit.rule);
或者它的实现MockitoAnnotations.initMocks(target);
误区一
上面的例子我们进行修改 ==> 为不同于上面的地方,如下:
运行发现如下错误:
|
|
这就是mock的误区一:
Mockito.mock()并不是mock一整个类,而是根据传进去的一个类,mock出属于这个类的一个对象,并且返回这个mock对象;而传进去的这个类本身并没有改变,用这个类new出来的对象也没有受到任何改变!
结合上面的例子,Mockito.mock(ArrayList.class);只是返回了一个属于ArrayList这个类的一个mock对象。ArrayList这个类本身没有受到任何影响,而 list 不是一个mock对象。Mockito.verify()的参数必须是mock对象,也就是说,Mockito只能验证mock对象的方法调用情况。因此,上面那种写法就出错了。
误区二
我们先看一个例子:
上面是一个登录操作, 现在我们来验证 login() 函数, 因为它没有返回值,这时候我们只要验证 execute() 有没有执行就可以了。
|
|
由于 UserLoginTask 继承 AsyncTask, 所以会报错,同误区一中的问题一样(not mock),这时候我们需要用到 Robolectric 框架,这个可以参考我以前写的。解决这个问题以后你会发现,fuck,怎么还有问题, 什么鬼。。
mock的误区二:
mock出来的对象并不会自动替换掉正式代码里面的对象,你必须要有某种方式把mock对象应用到正式代码里面。
这个时候我们可以通过 构造方式将依赖传进去,就 OK 了。
|
|
修改测试用例
运行发现终于成功了, 不容易。。
验证方法调用及参数
使用Mockito,验证一个对象的方法调用情况:
Mockito.verify(objectToVerify).methodToVerify(arguments);
其中 objectToVerify 和 methodToVerify 对应上面的 mockedList 和 add,表示验证 mockedList 的 add 方法是否传入参数是 one。
很多时候你并不关心被调用方法的参数具体是什么,或者是你也不知道,你只关心这个方法得到调用了就行。这种情况下,Mockito 提供了一系列的 any 方法,来表示任何的参数都行。
anyString() 表示任何一个字符串都可以。类似 anyString,还有 anyInt, anyLong, anyDouble 等等。anyObject 表示任何对象,any(clazz) 表示任何属于clazz的对象。 举个栗子:
指定 Mock 对象的某些方法的行为
那么接下来,我们就来介绍 Mock 的第二大作用,先介绍其中的第一点:指定 Mock 对象的某个方法返回特定的值。
我们见面的 login() 进行修改, 添加对网络的判断, 代码如下:
|
|
修改测试代码:
下面我们说说怎么样指定一个方法执行特定的动作,这个功能一般是用在目标的方法是 void 类型的时候。
现在假设我们的 LoginPresenter 的 login() 方法是这样的:
我们想进一步测试传给 NetworkCallback 里面的代码,验证 view 得到了更新等等。在测试环境下,我们并不想依赖 excute 的真实逻辑,而是让 mAuthTask
直接调用传入的 NetworkCallback 的 onSuccess 或 onFailed 方法。这种指定 Mock 对象执行特定的动作的写法如下:
`Mockito.doAnswer(desiredAnswer).when(mockObject).targetMethod(args);
测试代码如下:
|
|
我们想在调用某些无返回值函数的时候抛出异常,那么可以使用 doThrow 方法。如果想简单的指定目标方法“什么都不做”,那么可以使用 Mockito.doNothing()。如果你想让目标方法调用真实的逻辑,可以使用 Mockito.doCallRealMethod()(默认不是的, 请看下文)。
Spy
如果我们不指定 Mock 对象方法的行为, 那么他是不是走真实逻辑呢? 答案是否定的。如果没我们不指定它的行为,对于 Mock 对象的所有非 void 方法都将返回默认值 int,long 类型方法将返回0,boolean 方法将返回 false,对象方法将返回 null 等等;而 void 方法将什么都不做。
然而很多时候,你希望达到这样的效果:除非指定,否者调用这个对象的默认实现,同时又能拥有验证方法调用的功能。这正好是 spy 对象所能实现的效果。
创建Spy方式:
- Mockito.spy(toMockObject);
- 通过注解的方式@Spy
|
|
spy 与 mock 的唯一区别就是默认行为不一样: spy 对象的方法默认调用真实的逻辑,mock 对象的方法默认什么都不做,或直接返回默认值。
Mockito Annotation
通过 Mockito 注解可以快速创建 Mock 对象, 这样对于我们这样的懒人来说,是不是很爽。 上面我也简单提到了, 我们可以通过 mock() 和 @Mock 创建, @Mock 就是通过注解的方式创建的, 由于我们使用注解, 当然我们要告诉 Mockito 框架, 不然它怎样知道你使用注解了, 难道它是神吗? 加上@Rule 就行了, 这样JUnit Rule(?)就会每个测试方法测试前进行检查。添加方法:@Rule public MockitoRule mockitoRule = MockitoJUnit.rule();
, 当然创建 spy 对象也可以加 @Spy。
写在最后
上面的就是 Mockito 的基本使用, 当然由于并不是很全,更全面的可以看这里。基本的概念都有了, 下面的就是多用, 在项目中发现问题, 然后带着问题去查看文档。 Android 下还可以用 Dagger 动态依赖框架进行测试, 由于涉及到 Dagger 框架的使用,这个我们可以单独来说, 前提是你知道 Dagger 怎样用。
上面代码在 Github 上。
参考
http://www.vogella.com/tutorials/Mockito/article.html
http://chriszou.com/2016/04/29/android-unit-testing-mockito.html
https://medium.com/square-corner-blog/mockito-on-android-88f84656910