Android 单元测试 Mockito使用详解
简介
笔者的Android单元测试相关系列:
Android单元测试:Mockito使用详解
Android单元测试:使用本地数据测试Retrofit
Android单元测试:测试RxJava的同步及异步操作
Android 自动化测试 Espresso篇:简介&基础使用
Android 自动化测试 Espresso篇:异步代码测试
什么是mock测试,什么是mock对象?
先来看看下面这个示例:
从上图可以看出如果我们要对A进行测试,那么就要先把整个依赖树构建出来,也就是BCDE的实例。
一种替代方案就是使用mocks
从图中可以清晰的看出
mock对象就是在调试期间用来作为真实对象的替代品。
mock测试就是在测试过程中,对那些不容易构建的对象用一个虚拟对象来代替测试的方法就叫mock测试。
用四个字简单概括,就是「依赖隔离」。
Mockito简介
Mockito是一个流行的Mocking(模拟测试)框架,通过使用Mocking框架,可以尽可能使unit test独立的。unit test保持独立的好处不在这里讨论。
官方文档: http://docs.mockito.googlecode.com/hg/latest/org/mockito/Mockito.html
我们先来看如何添加Mockito的依赖:
//mockito
testCompile "org.mockito:mockito-core:2.8.9"
androidTestCompile "org.mockito:mockito-android:2.8.9"
ok,接下来我们就来看看怎样使用Mockito的API吧。
在Test代码中使用Mockito
初始化注入
首先我们在setUp函数中进行初始化:
private ArrayList mockList;@Before
public void setUp() throws Exception {//MockitoAnnotations.initMocks(this);//mock creationmockList = mock(ArrayList.class);
}
当然,你也这样这样进行注入:
@Mock
private ArrayList mockList;@Before
public void setUp() throws Exception {MockitoAnnotations.initMocks(this);
}
initMocks(this)后,就可以通过@Mock注解直接使用mock对象。
简单的例子
@Testpublic void sampleTest1() throws Exception {//使用mock对象执行方法mockList.add("one");mockList.clear();//检验方法是否调用verify(mockList).add("one"); verify(mockList).clear(); }
我们可以看到,我们可以直接调用mock对象的方法,比如ArrayList.add()或者ArrayList.clear(),然后我们通过verify函数进行校验。
直接mock接口对象
正常来讲我们想要一个接口类型的对象,首先我们需要先实例化一个对象并实现,其对应的抽象方法,但是有了mock,我们可以直接mock出一个接口对象:
@Test
public void sampleTest2() throws Exception {//我们可以直接mock一个借口,即使我们并未声明它MVPContract.Presenter mockPresenter = mock(MVPContract.Presenter.class);when(mockPresenter.getUserName()).thenReturn("qingmei2"); //我们定义,当mockPresenter调用getUserName()时,返回qingmei2String userName = mockPresenter.getUserName();verify(mockPresenter).getUserName(); //校验 是否mockPresenter调用了getUserName()方法Assert.assertEquals("qingmei2", userName); //断言 userName为qingmei2// verify(mockPresenter).getPassword(); //校验 是否mockPresenter调用了getPassword()方法String password = mockPresenter.getPassword(); //因为未定义返回值,默认返回nullverify(mockPresenter).getPassword();Assert.assertEquals(password, null);
}
参数匹配器
@Test
public void argumentMatchersTest3() throws Exception {when(mockList.get(anyInt())).thenReturn("不管请求第几个参数 我都返回这句");System.out.println(mockList.get(0));System.out.println(mockList.get(39));//当mockList调用addAll()方法时,「匹配器」如果传入的参数list size==2,返回true;when(mockList.addAll(argThat(getListMatcher()))).thenReturn(true);//根据API文档,我们也可以使用lambda表达式: 「匹配器」如果传入的参数list size==3,返回true;
// when(mockList.addAll(argThat(list -> list.size() == 3))).thenReturn(true);//我们不要使用太严格的参数Matcher,也许下面会更好
// when(mockList.addAll(argThat(notNull()));boolean b1 = mockList.addAll(Arrays.asList("one", "two"));boolean b2 = mockList.addAll(Arrays.asList("one", "two", "three"));verify(mockList).addAll(argThat(getListMatcher()));Assert.assertTrue(b1);Assert.assertTrue(!b2);
}private ListOfTwoElements getListMatcher() {return new ListOfTwoElements();
}/*** 匹配器,用来测试list是否有且仅存在两个元素*/
class ListOfTwoElements implements ArgumentMatcher<List> {public boolean matches(List list) {return list.size() == 2;}public String toString() {//printed in verification errorsreturn "[list of 2 elements]";}
}
对于一个Mock的对象,有时我们需要进行校验,但是基础的API并不能满足我们校验的需要,我们可以自定义Matcher,比如案例中,我们自定义一个Matcher,只有容器中两个元素时,才会校验通过。
验证方法的调用次数
/*** 我们也可以测试方法调用的次数* https://static.javadoc.io/org.mockito/mockito-core/2.8.9/org/mockito/Mockito.html#exact_verification** @throws Exception*/
@Test
public void simpleTest4() throws Exception {mockList.add("once");mockList.add("twice");mockList.add("twice");mockList.add("three times");mockList.add("three times");mockList.add("three times");verify(mockList).add("once"); //验证mockList.add("once")调用了一次 - times(1) is used by defaultverify(mockList, times(1)).add("once");//验证mockList.add("once")调用了一次//调用多次校验verify(mockList, times(2)).add("twice");verify(mockList, times(3)).add("three times");//从未调用校验verify(mockList, never()).add("four times");//至少、至多调用校验verify(mockList, atLeastOnce()).add("three times");verify(mockList, atMost(5)).add("three times");
// verify(mockList, atLeast(2)).add("five times"); //这行代码不会通过
}
抛出你想要的异常
/*** 异常抛出测试* https://static.javadoc.io/org.mockito/mockito-core/2.8.9/org/mockito/Mockito.html#stubbing_with_exceptions*/
@Test
public void throwTest5() {doThrow(new NullPointerException("throwTest5.抛出空指针异常")).when(mockList).clear();doThrow(new IllegalArgumentException("你的参数似乎有点问题")).when(mockList).add(anyInt());mockList.add("string");//这个不会抛出异常mockList.add(12);//抛出了异常,因为参数是IntmockList.clear();
}
如案例所示,当mockList对象执行clear方法时,抛出空指针异常,当其执行add方法,且传入的参数类型为int时,抛出非法参数异常。
校验方法执行顺序
/*** 验证执行执行顺序* https://static.javadoc.io/org.mockito/mockito-core/2.8.9/org/mockito/Mockito.html#in_order_verification** @throws Exception*/
@Test
public void orderTest6() throws Exception {List singleMock = mock(List.class);singleMock.add("first add");singleMock.add("second add");InOrder inOrder = inOrder(singleMock);//inOrder保证了方法的顺序执行inOrder.verify(singleMock).add("first add");inOrder.verify(singleMock).add("second add");List firstMock = mock(List.class);List secondMock = mock(List.class);firstMock.add("first add");secondMock.add("second add");InOrder inOrder1 = inOrder(firstMock, secondMock);//下列代码会确认是否firstmock优先secondMock执行add方法inOrder1.verify(firstMock).add("first add");inOrder1.verify(secondMock).add("second add");
}
有时候我们需要校验方法执行顺序的先后,如案例所示,inOrder对象会判断方法执行顺序,如果顺序不对,该测试案例failed。
确保mock对象从未进行过交互
/*** 确保mock对象从未进行过交互* https://static.javadoc.io/org.mockito/mockito-core/2.8.9/org/mockito/Mockito.html#never_verification** @throws Exception*/
@Test
public void noInteractedTest7() throws Exception {List firstMock = mock(List.class);List secondMock = mock(List.class);List thirdMock = mock(List.class);firstMock.add("one");verify(firstMock).add("one");verify(firstMock, never()).add("two");firstMock.add(thirdMock);// 确保交互(interaction)操作不会执行在mock对象上
// verifyZeroInteractions(firstMock); //test failed,因为firstMock和其他mock对象有交互verifyZeroInteractions(secondMock, thirdMock); //test pass
}
可能是因为水平有限,笔者很少用到这个API(好吧除了学习案例中用过其他基本没怎么用过),不过还是敲一遍,保证有个基础的印象。
简化mock对象的创建
/*** 简化mock对象的创建,请注意,一旦使用@Mock注解,一定要在测试方法调用之前调用(比如@Before注解的setUp方法)* MockitoAnnotations.initMocks(testClass);*/
@Mock
List mockedList;
@Mock
User mockedUser;@Test
public void initMockTest8() throws Exception {mockedList.add("123");mockedUser.setLogin("qingmei2");
}
注释写的很明白了,不赘述
方法连续调用测试
/*** 方法连续调用的测试* https://static.javadoc.io/org.mockito/mockito-core/2.8.9/org/mockito/Mockito.html#stubbing_consecutive_calls*/
@Test
public void continueMethodTest9() throws Exception {when(mockedUser.getName()).thenReturn("qingmei2").thenThrow(new RuntimeException("方法调用第二次抛出异常")).thenReturn("qingemi2 第三次调用");//另外一种方式when(mockedUser.getName()).thenReturn("qingmei2 1", "qingmei2 2", "qingmei2 3");String name1 = mockedUser.getName();try {String name2 = mockedUser.getName();} catch (Exception e) {System.out.println(e.getMessage());}String name3 = mockedUser.getName();System.out.println(name1);System.out.println(name3);
}
有用,但不重要,学习一下加深印象。
为回调方法做测试
/*** 为回调方法做测试* https://static.javadoc.io/org.mockito/mockito-core/2.8.9/org/mockito/Mockito.html#answer_stubs*/
@Test
public void callBackTest() throws Exception {when(mockList.add(anyString())).thenAnswer(new Answer<Boolean>() {@Overridepublic Boolean answer(InvocationOnMock invocation) throws Throwable {Object[] args = invocation.getArguments();Object mock = invocation.getMock();return false;}});System.out.println(mockList.add("第1次返回false"));//lambda表达式when(mockList.add(anyString())).then(invocation -> true);System.out.println(mockList.add("第2次返回true"));when(mockList.add(anyString())).thenReturn(false);System.out.println(mockList.add("第3次返回false"));
}
在Mockito的官方文档中,这样写道:
在最初的Mockito里也没有这个具有争议性的特性。我们建议使用thenReturn() 或thenThrow()来打桩。这两种方法足够用于测试或者测试驱动开发。
实际上笔者日常开发中也不怎么用到这个特性。
拦截方法返回值(常用)
/*** doReturn()、doThrow()、doAnswer()、doNothing()、doCallRealMethod()系列方法的运用* https://static.javadoc.io/org.mockito/mockito-core/2.8.9/org/mockito/Mockito.html#do_family_methods_stubs*/
@Test
public void returnTest() throws Exception {//返回值为null的函数,可以通过这种方式进行测试doAnswer(invocation -> {System.out.println("测试无返回值的函数");return null;}).when(mockList).clear();doThrow(new RuntimeException("测试无返回值的函数->抛出异常")).when(mockList).add(eq(1), anyString());doNothing().when(mockList).add(eq(2), anyString());// doReturn("123456").when(mockList).add(eq(3), anyString()); //不能把空返回值的函数与doReturn关联mockList.clear();mockList.add(2, "123");mockList.add(3, "123");mockList.add(4, "123");mockList.add(5, "123");//但是请记住这些add实际上什么都没有做,mock对象中仍然什么都没有System.out.print(mockList.get(4));
}
我们不禁这样想,这些方法和when(mock.do()).thenReturn(foo)这样的方法有什么区别,或者说,这些方法有必要吗?
答案是肯定的,因为在接下来介绍的新特性Spy中,该方法起到了至关重要的作用。
可以说,以上方法绝对是不可代替的。
Spy:监控真实对象(重要)
/*** 监控真实对象* https://static.javadoc.io/org.mockito/mockito-core/2.8.9/org/mockito/Mockito.html#spy*/
@Test
public void spyTest() throws Exception {List list = new ArrayList();List spyList = spy(list);//当spyList调用size()方法时,return100when(spyList.size()).thenReturn(100);spyList.add("one");spyList.add("two");System.out.println("spyList第一个元素" + spyList.get(0));System.out.println("spyList.size = " + spyList.size());verify(spyList).add("one");verify(spyList).add("two");//请注意!下面这行代码会报错! java.lang.IndexOutOfBoundsException: Index: 10, Size: 2//不可能 : 因为当调用spy.get(0)时会调用真实对象的get(0)函数,此时会发生异常,因为真实List对象是空的
// when(spyList.get(10)).thenReturn("ten");//应该这么使用doReturn("ten").when(spyList).get(9);doReturn("eleven").when(spyList).get(10);System.out.println("spyList第10个元素" + spyList.get(9));System.out.println("spyList第11个元素" + spyList.get(10));//Mockito并不会为真实对象代理函数调用,实际上它会拷贝真实对象。因此如果你保留了真实对象并且与之交互//不要期望从监控对象得到正确的结果。当你在监控对象上调用一个没有被stub的函数时并不会调用真实对象的对应函数,你不会在真实对象上看到任何效果。//因此结论就是 : 当你在监控一个真实对象时,你想在stub这个真实对象的函数,那么就是在自找麻烦。或者你根本不应该验证这些函数。
}
Spy绝对是一个好用的功能,我们不要滥用,但是需要用到对真实对象的测试操作,spy绝对是一个很不错的选择。
捕获参数(重要)
/*** 为接下来的断言捕获参数(API1.8+)* https://static.javadoc.io/org.mockito/mockito-core/2.8.9/org/mockito/Mockito.html#captors*/
@Test
public void captorTest() throws Exception {Student student = new Student();student.setName("qingmei2");ArgumentCaptor<Student> captor = ArgumentCaptor.forClass(Student.class);mockList.add(student);verify(mockList).add(captor.capture());Student value = captor.getValue();Assert.assertEquals(value.getName(),"qingmei2");
}@Data
private class Student {private String name;
}
我们将定义好的ArgumentCaptor参数捕获器放到我们需要去监控捕获的地方,如果真的执行了该方法,我们就能通过captor.getValue()中取到参数对象,如果没有执行该方法,那么取到的只能是null或者基本类型的默认值。
小结
本文看起来是枯燥无味的,事实上也确实如此,但是如果想在开发中写出高覆盖率的单元测试,Mockito强大的功能一定能让你学会之后爱不释手。
在接下来的文章中,笔者会通过实际案例,阐述自己在实际的Android APP开发过程中,MVP+Rxjava+Retrofit模式下如何进行单元测试的编写。
参考文档
Mocktio 2.8.9 API 官方文档
Mocktio 2.8.9 API 中文文档
案例代码:
本文所有案例代码,点我查看
Android 单元测试 Mockito使用详解相关推荐
- 史上最全Android build.gradle配置详解
Android Studio是采用gradle来构建项目的,gradle是基于groovy语言的,如果只是用它构建普通Android项目的话,是可以不去学groovy的.当我们创建一个Android项 ...
- android jar 包 意见反馈功能,android重点jar包详解.docx
android重点jar包详解 深入理解View(一):从setContentView谈起 我们都知道?MVC,在Android中,这个?V?即指View,那我们今天就来探探View的究竟.在onCr ...
- Android应用坐标系统全面详解
Android应用坐标系统全面详解 原文链接:CSDN@工匠若水,http://blog.csdn.net/yanbober/article/details/50419117 1. 背景 去年有很多人 ...
- Android NFC开发实战详解
Android NFC开发实战详解 Android开发实战详解NFC国内第一本AndroidNFC开发书籍带你开启AndroidNFC开发的神秘之旅大综合案例帮助读者快速进入实战角色:WiFi快速连接 ...
- Android Gradle 自定义Task详解二:进阶
转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/78523958 本文出自[赵彦军的博客] 系列目录 Android Gradle使用 ...
- Android Gradle 自定义Task 详解
转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/76408024 本文出自[赵彦军的博客] 系列目录 Android Gradle使用 ...
- android ------- 开发者的 RxJava 详解
在正文开始之前的最后,放上 GitHub 链接和引入依赖的 gradle 代码: Github: https://github.com/ReactiveX/RxJava https://githu ...
- Android自定义属性,format详解
1. reference:参考某一资源ID. (1)属性定义: <declare-styleable name="名称"><attr name="bac ...
- android layout_width 属性,android:layout_weight属性详解
在android开发中LinearLayout很常用,LinearLayout的内控件的android:layout_weight在某些场景显得非常重要,比如我们需要按比例显示.android并没用提 ...
最新文章
- UML和模式应用(1):面向对象的分析与设计
- Laravel提交POST请求报错
- VTK:图片之ImageSobel2D
- 如何查看文件夹里有几张图片_如何把几张图片合成一个pdf?图片合并为pdf的操作教程...
- [C++11]基于范围的for循环
- JS实现Ajax异步刷新
- xwpftablecell设置字体样式_HTML的文字样式
- java执行指定目录的class文件
- 交换机命令行配置与VLAN
- jdk 安装_Linux安装JDK操作手册
- LeetCode(872)——叶子相似的树(JavaScript)
- 《上市公司信息披露电子化规范》简介
- 架构师学习笔记(持续更新)
- oracle concepts中文,Oracle Concepts 中英文对照版 (10g R2)
- [转帖]Mootools源码分析-23 -- Selectors-2
- Redis常用命令、数据类型讲解
- 2022研究生数学建模ABCDEF思路
- 管理感悟:一种招聘考试的想法
- 物联网大赛作品-老人手环介绍
- 【总结】从0到1的项目经历