简介

笔者的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使用详解相关推荐

  1. 史上最全Android build.gradle配置详解

    Android Studio是采用gradle来构建项目的,gradle是基于groovy语言的,如果只是用它构建普通Android项目的话,是可以不去学groovy的.当我们创建一个Android项 ...

  2. android jar 包 意见反馈功能,android重点jar包详解.docx

    android重点jar包详解 深入理解View(一):从setContentView谈起 我们都知道?MVC,在Android中,这个?V?即指View,那我们今天就来探探View的究竟.在onCr ...

  3. Android应用坐标系统全面详解

    Android应用坐标系统全面详解 原文链接:CSDN@工匠若水,http://blog.csdn.net/yanbober/article/details/50419117 1. 背景 去年有很多人 ...

  4. Android NFC开发实战详解

    Android NFC开发实战详解 Android开发实战详解NFC国内第一本AndroidNFC开发书籍带你开启AndroidNFC开发的神秘之旅大综合案例帮助读者快速进入实战角色:WiFi快速连接 ...

  5. Android Gradle 自定义Task详解二:进阶

    转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/78523958 本文出自[赵彦军的博客] 系列目录 Android Gradle使用 ...

  6. Android Gradle 自定义Task 详解

    转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/76408024 本文出自[赵彦军的博客] 系列目录 Android Gradle使用 ...

  7. android ------- 开发者的 RxJava 详解

    在正文开始之前的最后,放上 GitHub 链接和引入依赖的 gradle 代码: Github:  https://github.com/ReactiveX/RxJava  https://githu ...

  8. Android自定义属性,format详解

    1. reference:参考某一资源ID. (1)属性定义: <declare-styleable name="名称"><attr name="bac ...

  9. android layout_width 属性,android:layout_weight属性详解

    在android开发中LinearLayout很常用,LinearLayout的内控件的android:layout_weight在某些场景显得非常重要,比如我们需要按比例显示.android并没用提 ...

最新文章

  1. UML和模式应用(1):面向对象的分析与设计
  2. Laravel提交POST请求报错
  3. VTK:图片之ImageSobel2D
  4. 如何查看文件夹里有几张图片_如何把几张图片合成一个pdf?图片合并为pdf的操作教程...
  5. [C++11]基于范围的for循环
  6. JS实现Ajax异步刷新
  7. xwpftablecell设置字体样式_HTML的文字样式
  8. java执行指定目录的class文件
  9. 交换机命令行配置与VLAN
  10. jdk 安装_Linux安装JDK操作手册
  11. LeetCode(872)——叶子相似的树(JavaScript)
  12. 《上市公司信息披露电子化规范》简介
  13. 架构师学习笔记(持续更新)
  14. oracle concepts中文,Oracle Concepts 中英文对照版 (10g R2)
  15. [转帖]Mootools源码分析-23 -- Selectors-2
  16. Redis常用命令、数据类型讲解
  17. 2022研究生数学建模ABCDEF思路
  18. 管理感悟:一种招聘考试的想法
  19. 物联网大赛作品-老人手环介绍
  20. 【总结】从0到1的项目经历

热门文章

  1. re:Invent十周年,亚马逊云科技诠释探路者精神
  2. 造福C站全体用户,文章漫游者v0.2开放下载
  3. My Andoid Tool 微信禁用方案记录
  4. 千兆路由器与1200M路由器区别?
  5. centos系统elasticseach安装
  6. i3 10100核显什么水平
  7. html5是播放什么中新,关于html5中标签video播放的新解析-
  8. 【总结】Apk反编译全解
  9. Zotero使用第三方云服务同步(Dropbox、OneDrive、Google Drive)
  10. CST微波工作室学习笔记—1.新建工程