Android 单元测试 Mockito使用详解
来源:互联网 发布:知荣制衣 联系方式 编辑:程序博客网 时间:2024/04/30 02:43
简介
笔者的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的依赖:
//mockitotestCompile "org.mockito:mockito-core:2.8.9"androidTestCompile "org.mockito:mockito-android:2.8.9"
ok,接下来我们就来看看怎样使用Mockito的API吧。
在Test代码中使用Mockito
初始化注入
首先我们在setUp函数中进行初始化:
private ArrayList mockList;@Beforepublic void setUp() throws Exception { //MockitoAnnotations.initMocks(this); //mock creation mockList = mock(ArrayList.class);}
当然,你也这样这样进行注入:
@Mockprivate ArrayList mockList;@Beforepublic void setUp() throws Exception { MockitoAnnotations.initMocks(this);}
initMocks(this)后,就可以通过@Mock注解直接使用mock对象。
简单的例子
@Test public 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出一个接口对象:
@Testpublic void sampleTest2() throws Exception { //我们可以直接mock一个借口,即使我们并未声明它 MVPContract.Presenter mockPresenter = mock(MVPContract.Presenter.class); when(mockPresenter.getUserName()).thenReturn("qingmei2"); //我们定义,当mockPresenter调用getUserName()时,返回qingmei2 String userName = mockPresenter.getUserName(); verify(mockPresenter).getUserName(); //校验 是否mockPresenter调用了getUserName()方法 Assert.assertEquals("qingmei2", userName); //断言 userName为qingmei2// verify(mockPresenter).getPassword(); //校验 是否mockPresenter调用了getPassword()方法 String password = mockPresenter.getPassword(); //因为未定义返回值,默认返回null verify(mockPresenter).getPassword(); Assert.assertEquals(password, null);}
参数匹配器
@Testpublic 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 errors return "[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 */@Testpublic 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 default verify(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 */@Testpublic void throwTest5() { doThrow(new NullPointerException("throwTest5.抛出空指针异常")).when(mockList).clear(); doThrow(new IllegalArgumentException("你的参数似乎有点问题")).when(mockList).add(anyInt()); mockList.add("string");//这个不会抛出异常 mockList.add(12);//抛出了异常,因为参数是Int mockList.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 */@Testpublic 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 */@Testpublic 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); */@MockList mockedList;@MockUser mockedUser;@Testpublic 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 */@Testpublic 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 */@Testpublic void callBackTest() throws Exception { when(mockList.add(anyString())).thenAnswer(new Answer<Boolean>() { @Override public 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 */@Testpublic 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 */@Testpublic void spyTest() throws Exception { List list = new ArrayList(); List spyList = spy(list); //当spyList调用size()方法时,return100 when(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 */@Testpublic 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");}@Dataprivate 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单元测试-Mockito的使用
- 使用Mockito和Roboletric进行Android单元测试
- Android单元测试(二):Mockito框架的使用
- Android单元测试-Mockito
- 使用Mockito进行单元测试
- Android-使用Mockito、Robolectric和RxJava及Retrofit进行单元测试
- Android单元测试(四):Mock以及Mockito的使用
- Android本地单元测试-mockito的使用(Kotlin版本)
- 使用Mockito进行java单元测试
- 使用Mockito进行Java单元测试
- Mockito java单元测试框架使用
- 单元测试:Mockito
- Mockito单元测试
- Android 单元测试之JUnit和Mockito
- 二.Android单元测试 Mockito的更多用法
- 一.Android单元测试 Mockito的简单用法
- Spring中如何使用Mockito做单元测试
- V4L2设置摄像头光照,对比度参数
- 5-1 列出连通集 (25分)
- Xamarin.Forms 用户界面——控件——Text——Label
- CentOS MongoDB安装与使用
- Matlab2014a for MAC安装Piotr's Image & Video Matlab Toolbox
- Android 单元测试 Mockito使用详解
- 【GDOI2018模拟8.7】的士碰撞
- 使用Java Rest Client操作Elasticsearch
- spring mvc 使用注意事项
- 简易的省市二级联动
- mysql数据查询优化。
- iOS自适应手机语言的国家&国旗列表
- Myeclipse的tomcat配置(详细配图)
- string