Mockito

本文中API文档部分,翻译自:Mockito
水平有限自己感觉很多地方表达的并不到位,但找不到更好的表达方式,如果您觉着有更好的表达方式,帮助我改进!

Mockito 是什么?

Mockito 是一个开源的Java测试框架,它能够Mock对象、验证结果以及为测试用例打桩。

Mockito 有什么特点?

Mockito 在运行时Mock对象,并且模拟被测试对象的行为,从而达到消除依赖的效果。

Mockito 的API文档翻译

下面的例子mock一个list,因为很多人已经熟悉接口(比如,add() , get() , clear()方法)。在实际开发中,请不要mock List类。使用一个真实的实例来代替。

1. 让我们验证某些行为:

    //通过静态导入的方式,可以让代码看起来很干净
     import static org.mockito.Mockito.*;

     //创建mock对象
     List mockedList = mock(List.class);

     //使用mock对象
     mockedList.add("one");
     mockedList.clear();

     //验证函数的调用次数
     verify(mockedList).add("one");
     verify(mockedList).clear();
Mock对象一旦被创建后,它将会记住所有和它进行的交互。这样你就可以选择任何你感兴趣的交互来进行验证。

2. 如何做一些测试桩(stubbing)

    //mock具体的类型,不仅只是接口
    LinkedList mockedList = mock(LinkedList.class);

     //打测试桩,当调用get(0)时,返回“first”
     when(mockedList.get(0)).thenReturn("first");
     //打测试桩,当调用get(1)时,抛出异常
     when(mockedList.get(1)).thenThrow(new RuntimeException());

     //调用get(0)输出"first"
     System.out.println(mockedList.get(0));

     //调用get(1)抛出异常
     System.out.println(mockedList.get(1));

     //调用get(999),打印出“null”,因为get(999)没有被打桩
     System.out.println(mockedList.get(999));

     //尽管它可以验证一个测试桩的调用,但是通常情况下它是多余的。
     //如果你的代码关心get(0)返回的结果,那么一些事将会来打算(经常在verify()前执行。)
     //如果你的代码不关心get(0)返回的结果,那么他不应该被用来作为测试桩。不信?看这里。
     verify(mockedList).get(0);
默认情况下,所有的函数都有返回值。一个mock函数返回的可能是null , 或者一个空的集合或者是一个包装的内置基础类型,这视情况而定。例如,0对应的Integer 和 false对应的Boolean.

3. 参数匹配器

Mockito 以自然的Java风格验证参数值:通过使用equals()方法。需要额外的灵活性的时候,你可能需要使用参数匹配器:

     //使用内置的anyInt()参数匹配器,当调用get(整型值)时都返回“element”
     when(mockedList.get(anyInt())).thenReturn("element");

     //使用自定义的参数匹配器(也就是说,调用isValid()返回你自己的匹配器实现)
     when(mockedList.contains(argThat(isValid()))).thenReturn("element");

     //输出“element”
     System.out.println(mockedList.get(999));

     //你也可以验证参数匹配器
     verify(mockedList).get(anyInt());

参数匹配器允许灵活的验证或者测试桩。点击这里查看更多的内置参数匹配器和自定义参数匹配器的示例 或者是 hamcrest matchers

查看ArgumentMatcher的javadoc来查看自定义参数匹配器的信息。

使用复杂的参数匹配器是合乎情理的。equals() 和 anyX()提供的自然的匹配方式可以让代码更干净,整洁。有时候最好重构代码来使用equals()匹配或者实现equals()函数来帮助你进行测试。

另外, 阅读section 15或者ArgumentCaptorjavadoc文档。ArgumentCaptor是参数匹配器的一个特殊实现,他可以以后的断言捕获参数值。

使用参数匹配器的注意事项:

如果你使用参数匹配器,所有的参数都必须由匹配器提供。下面的例子展示了验证,但是是同一个测试桩的验证:

       verify(mock).someMethod(anyInt(), anyString(), eq("third argument"));
       //上面的代码是正确的 - eq() 也是一个参数匹配器

       verify(mock).someMethod(anyInt(), anyString(), "third argument");
       //上面的代码是错误的 - 将会抛出异常,因为第三个参数没有提供参数匹配器。

像anyObject() ,eq() 这些参数匹配器方法不会返回参数匹配器。在内部,他们将会把参数匹配器记录在一个栈中然后返回一个假的值,通常为null。这样的实现是由于Java编译器提供的静态类型安全检查。这样的结果就是你不能再验证或者测试桩方法之外使用anyObject(), eq()方法。

4. 验证确切的调用次数,最少调用、从未调用:


         //使用mock
         mockedList.add("once");

         mockedList.add("twice");
         mockedList.add("twice");

         mockedList.add("three times");
         mockedList.add("three times");
         mockedList.add("three times");

         //下面的两个验证函数的验证效果一样,因为verify默认验证的就是times(1)
         verify(mockedList).add("once");
         verify(mockedList, times(1)).add("once");

         //验证具体的执行次数
         verify(mockedList, times(2)).add("twice");
         verify(mockedList, times(3)).add("three times");

         //使用never() 进行验证,never()相当于times(0)
         verify(mockedList, never()).add("never happened");

         //使用atLeast()/atMost()来进行验证
         verify(mockedList, atLeastOnce()).add("three times");
         verify(mockedList, atLeast(2)).add("five times");
         verify(mockedList, atMost(5)).add("three times");

默认情况下是times(1)。因此times(1)通常被省略。times可以判断函数被调用的确切次数,atLeast表示至少调用的次数,atMost表示最多调用的次数,never则表示从未调用,通过他们可以判断一个函数被调用的次数。

5. 对返回值为空的函数插桩,调用会抛出异常

         doThrow(new RuntimeException()).when(mockedList).clear();

         //调用clear() 会抛出 RuntimeException()
         mockedList.clear();

阅读本文第12章节的doTrow|doAnswer系列方法。最初,stubVoid(Object)被用来为void进行插桩。现在stubVoid()方法已经弃用了,推荐使用doThrow(Throwable…)。这是为了提高可读性和doAnswer(Answer)系列方法的一致性。

6. 按顺序来验证

         // A. 单个mock对象他的方法必须按照顺序来调用。
         List singleMock = mock(List.class);

         //使用单个mock
         singleMock.add("was added first");
         singleMock.add("was added second");

         //为单个Mock创建一个InOrder的顺序验证
         InOrder inOrder = inOrder(singleMock);

         //下面确保第一次调用的是“was added first”,然后是"was added second"
         inOrder.verify(singleMock).add("was added first");
         inOrder.verify(singleMock).add("was added second");

         // B. 多个mock也必须按照顺序来使用
         List firstMock = mock(List.class);
         List secondMock = mock(List.class);

         //使用mock
         firstMock.add("was called first");
         secondMock.add("was called second");

         //创建一个inOrder对象,把需要按照顺序验证的mock传递进去。
         InOrder inOrder = inOrder(firstMock, secondMock);

         //保证firstMock在secondMock之前调用
         inOrder.verify(firstMock).add("was called first");
         inOrder.verify(secondMock).add("was called second");

按顺序验证是非常灵活的——你不需要挨个的验证所有的交互,这样你只需要按顺序验证你感兴趣的mock即可。并且,你也可以创建一个InOrder对象把有关联的mock传递进去,进行顺序验证。

7. 确保交互操作没有执行在Mock对象上

          //使用mock对象——仅和mockOne在进行交互
         mockOne.add("one");

         //普通验证
         verify(mockOne).add("one");

         //验证某个交互是否从未被执行
         verify(mockOne, never()).add("two");

         //验证其它Mock对象没有交互过
         verifyZeroInteractions(mockTwo, mockThree);

8. 查找多余的调用

         //使用mock
        mockedList.add("one");
         mockedList.add("two");

         verify(mockedList).add("one");

         //下面的验证将会失败
         verifyNoMoreInteractions(mockedList);

9. 快速创建mock对象——@Mock注解

  • 减少创建mock的重复代码
  • 让测试类更易读
  • 让查证错误正容易因为字段名被用来标识mock对象。
    public class ArticleManagerTest {

        @Mock private ArticleCalculator calculator;
        @Mock private ArticleDatabase database;
        @Mock private UserProvider userProvider;

        private ArticleManager manager;

非常重要的一点!下面的代码需要配置在base类或者是一个测试runner里:

         MockitoAnnotations.initMocks(testClass);

(译者注:在使用@Mock注解时需要在使用这些Mock对象之前调用MockitoAnnotations.initMocks(testClass)函数来进行初始化,调用该函数之后,所有使用@Mock注解的对象就会被创建。)

你可以使用内置的runner:MockitoJUnitRunner 或者一个规则: MockitoRule.
更多信息阅读这里: MockitoAnnotations

10. 为连续的调用做测试桩(迭代式的测试桩)

有时我们需要为同一个方法调用的返回值或者异常做测试桩。典型的运用就是使用Mock迭代器。Mockito的早期版本是没有这个特性的。比如,开发人员可能会使用Iterable或者是简单的集合来替代迭代器。这些方法为做测试桩提供了更自然的方式,在一些场景中为连续的调用做测试桩会很有用,比如:

         when(mock.someMethod("some arg"))
         .thenThrow(new RuntimeException())
         .thenReturn("foo");

         //第一次调用:抛出RuntimeException;
         mock.someMethod("some arg");

         //第二次调用:打印“foo”
         System.out.println(mock.someMethod("some arg"));

         //后续的所有调用也是打印“foo”
         System.out.println(mock.someMethod("some arg"));

另外,连续调用的另一种更简短的版本:

         when(mock.someMethod("some arg"))
           .thenReturn("one", "two", "three");

11. 为回调做测试桩

为泛型接口Answer打桩。

但是另一个有争议的特性在刚开始是没有被引入到Mockito的。我们推荐简单的调用thenReturn() 或者 thenThrow(),他应该能够满足TTD中简洁代码。然而,如果你需要为一个泛型接口做测试桩,这有个例子:

        when(mock.someMethod(anyString())).thenAnswer(new Answer() {
         Object answer(InvocationOnMock invocation) {
            //获得函数调用的参数
             Object[] args = invocation.getArguments();
            //获得Mock对象本身
             Object mock = invocation.getMock();
             return "called with arguments: " + args;
         }
         });

         //输出: "called with arguments: foo"
         System.out.println(mock.someMethod("foo"));

12. doReturn()|doThrow()| doAnswer()|doNothing()|doCallRealMethod() 系列方法

为无返回值的方法打桩需要从when(Object)变化来的不同的方法,因为编译器不识别无返回值的函数在一个括号里。

doThrow(Throwable…)替代了stubVoid(Object)方法为无返回值的函数打桩。主要原因是为了提高可读性和保持doAnswer()系列方法的一致性。

当你想为一个带有异常的void函数打桩时可以使用doThrow函数,实例代码如下:

        doThrow(new RuntimeException()).when(mockedList).clear();

        //下面的代码会抛出异常:
         mockedList.clear();

当你调用doThrow(), doAnswer(), doNothing(), doReturn() and doCallRealMethod() 这些函数时,可以在适当的位置调用when()函数,这样当触发when中指定的函数时就会执行doXxx中的行为。

当你需要下面这些功能时这是必须要做的:

* 为void函数打桩
* 为受监控对象的方法打桩
* 不止一次的测试同一个函数,在测试过程中改变Mock对象的行为。

但是你可能更喜欢使用这些方法和相匹配的when(),来对测试你的所有的测试桩调用

了解更多关于这些方法的信息:

doReturn(Object)

doThrow(Throwable…)

doThrow(Class)

doAnswer(Answer)

doNothing()

doCallRealMethod()

13. 监控真实对象

你可以为真实对象创建一个监控(spy)对象。当你使用这个spy对象时,真实的方法也会调用(除非这个函数被打桩)。实际中应该有节制的spy对象,比如在处理遗留代码时:

           List list = new LinkedList();
           List spy = spy(list);

           //你可以随意的为某些函数打桩:
           when(spy.size()).thenReturn(100);

           //通过spy对象调用真实对象的方法:
           spy.add("one");
           spy.add("two");

           //打印list的第一个元素
           System.out.println(spy.get(0));

           //因为size()函数被打桩了,所以这里返回100
           System.out.println(spy.size());

           //验证交互
           verify(spy).add("one");
           verify(spy).add("two");

理解监控真实对象非常重要!

  • 有时候,在监控对象上使用when(Object)来进行打桩是不可能或者不切实际的。因此在使用spy的时候,请考虑doReturn|Answer|Throw() 这一系列的方法来打桩。例如:

    
       List list = new LinkedList();
       List spy = spy(list);
    
       //不可能:因为当调用spy.get(0)时会调用真实对象的get(0)函数,此时会发生IndexOutOfBoundsException,因为真实list对象是空的。
       when(spy.get(0)).thenReturn("foo");
    
       //你需要使用doReturn来进行打桩。
       doReturn("foo").when(spy).get(0);
    
  • Mockito并不会为真实对象代理函数调用,实际上它会复制真实对象。所以,如果你保留了真实对象并与其交互,不要期望从监控对象得到正确的结果。当你在监控对象上调用一个没有被打桩的函数时,并不会调用真实对象的对应函数,因此你不会在真实对象上看到任何结果。

  • 因此结论就是:当你在监控一个真实对象时,你想为这个真实对象的函数做测试桩,那么就是在自找麻烦。或者你根本不应该验证这些函数。

Mockito 怎么用:

  1. 使用Mockito对异步方法进行单元测试
  2. Android 单元测试: 首先,从是什么开始
  3. Android单元测试(二):再来谈谈为什么
  4. Android单元测试(三):JUnit单元测试框架的使用
  5. Android单元测试(四):Mock以及Mockito的使用
  6. Java Mocking入门—使用Mockito

参考:

  1. android.test.mock

  2. 本文中API文档部分,翻译自:Mockito

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值