Mockito 测试基本用法

来源:互联网 发布:windows 扩展屏幕 ipad 编辑:程序博客网 时间:2024/06/10 00:20

一、什么是mock测试,什么是mock对象?

先来看看下面这个示例:

这里写图片描述

从上图可以看出如果我们要对A进行测试,那么就要先把整个依赖树构建出来,也就是BCDE的实例。

一种替代方案就是使用mocks

这里写图片描述

从图中可以清晰的看出

mock对象就是在调试期间用来作为真实对象的替代品。
mock测试就是在测试过程中,对那些不容易构建的对象用一个虚拟对象来代替测试的方法就叫mock测试。

知道什么是mock测试后,那么我们就来认识一下mock框架—Mockito

二、什么是Mockito

除了有一个好记的名字外,Mockito尝试用不一样的方法做mocking测试,是简单轻量级能够替代EasyMock的框架。使用简单,测试代码可读性高,丰富的文档包含在javadoc中,直接在IDE中可查看文档,实例,说明。更多信息:http://code.google.com/p/mockito/

三、Stub和Mock

相同点:Stub和Mock对象都是用来模拟外部依赖,使我们能控制。

不同点:
- 而stub完全是模拟一个外部依赖,用来提供测试时所需要的测试数据。
- 而mock对象用来判断测试是否能通过,也就是用来验证测试中依赖对象间的交互能否达到预期。
- 在mocking框架中mock对象可以同时作为stub和mock对象使用,两者并没有严格区别。

更多信息:http://martinfowler.com/articles/mocksArentStubs.html

四、mockito入门实例

  1. Maven依赖:
<dependency>  <groupId>org.mockito</groupId>    <artifactId>mockito-all</artifactId>    <version>1.10.19</version></dependency>
  1. 基本用法
    @Test    @SuppressWarnings("unchecked")    public void testSimple() throws Exception {        //创建mock对象        /*创建mock对象不能对final,Anonymous ,primitive类进行mock。*/        List<String> list = mock(List.class);        //设置方法的预期返回值        when(list.get(0)).thenReturn("hello world!");        //doReturn("hello wolrd!").when(list).get(0);        //可以设定返回异常        when(list.get(1)).thenThrow(new RuntimeException("exception!"));        //没有返回值的void方法与其设定(支持迭代风格,第一次调用donothing,第二次dothrow抛出runtime异常)        doNothing().doThrow(new RuntimeException("void exception")).when(list).clear();        list.clear();        list.clear();        verify(list, times(2)).clear();        String result = list.get(0);        //验证方法调用        verify(list).get(0);        //junit测试        Assert.assertEquals("hello world!", result);    }

Ps:
创建mock对象不能对final,Anonymous ,primitive类进行mock。

五、参数匹配器(Argument Matcher)

Matchers类内加你有很多参数匹配器 anyInt、anyString、anyMap…..Mockito类继承于Matchers,Stubbing时使用内建参数匹配器,下例:

   /**     * 参数匹配器     * 如果使用参数匹配器,那么所有的参数都要使用参数匹配器,不管是stubbing还是verify的时候都一样。     */    @Test    @SuppressWarnings("unchecked")    public void argumentMatcherTest() throws Exception {        List<String> list = mock(List.class);        when(list.get(anyInt())).thenReturn("hello", "world", "hqq");        String result = list.get(0) + list.get(1);        verify(list, times(2)).get(anyInt());        Assert.assertEquals("helloworld", result);        Map<Integer, String> map = mock(Map.class);        when(map.put(anyInt(), anyString())).thenReturn("hello");        String str = map.put(1, "world");        verify(map).put(eq(1), eq("world"));        Assert.assertEquals("hello", str);    }

需要注意的是:如果使用参数匹配器,那么所有的参数都要使用参数匹配器,不管是stubbing还是verify的时候都一样。

六、方法调用的验证(具体的调用次数、至少一次,一次也没有)

   /**     * 方法调用的验证     */    @Test    public void verifyInvocate() throws Exception {        List<String> list = mock(List.class);        list.add("one");        list.add("twice");        list.add("twice");        list.add("three times");        list.add("three times");        list.add("three times");        verify(list, times(1)).add("one");        verify(list, times(2)).add("twice");        verify(list, times(3)).add("three times");        verify(list, times(0)).add("never added");        verify(list, atLeastOnce()).add("one");        verify(list, atLeast(2)).add("twice");        verify(list, atMost(3)).add("three times");    }

七、按顺序来验证

    /**     * 按顺序来验证     */    @Test    @SuppressWarnings("unchecked")    public void orderTest() throws Exception {        // A. 单个mock对象他的方法必须按照顺序来调用。        List singleMock = mock(List.class);        singleMock.add("first");        singleMock.add("second");        //为单个Mock创建一个InOrder的顺序验证        InOrder inOrderSingle = inOrder(singleMock);        //验证访问次序        inOrderSingle.verify(singleMock).add("first");        inOrderSingle.verify(singleMock).add("second");        // B. 多个mock也必须按照顺序来使用        List firstMock = mock(List.class);        List secondMock = mock(List.class);        firstMock.add("first add");        secondMock.add("second add");        InOrder inOrderMulti = inOrder(firstMock, secondMock);        inOrderMulti.verify(firstMock).add("first add");        inOrderMulti.verify(secondMock).add("second add");    }

八、 确保交互操作没有执行在Mock对象上

//使用mock对象——仅和mockOne在进行交互mockOne.add("one");//普通验证verify(mockOne).add("one");//验证某个交互是否从未被执行verify(mockOne, never()).add("two");//验证其它Mock对象没有交互过verifyZeroInteractions(mockTwo, mockThree);

九、查找多余的调用

//使用mockmockedList.add("one");mockedList.add("two");verify(mockedList).add("one");//下面的验证将会失败verifyNoMoreInteractions(mockedList);

十、快速创建mock对象——@Mock注解

@RunWith(MockitoJUnitRunner.class)public class AnnotationMockTest {    @Mock    List<String> list;    @Mock    private User user;    @Test    public void test() throws Exception {        when(list.get(0)).thenReturn("hello,mockito");        System.out.println(list.get(0));    }    @Test    public void userTest() throws Exception {        when(user.getName()).thenReturn("heqianqian");        System.out.println(user.getName());    }}

十一、迭代式的测试

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

@RunWith(MockitoJUnitRunner.class)public class IteratorMockitoTest {    @Mock    List<String> list;    @Test    public void iteratorTest() throws Exception {        when(list.get(0)).thenReturn("one","two","three");        //返回one        System.out.println(list.get(0));        //返回two        System.out.println(list.get(0));        //之后都是three        System.out.println(list.get(0));        System.out.println(list.get(0));    }}

十二、为回调做测试桩

为泛型接口Answer打桩。

public class CallbackMockitoTest {    @Test    @SuppressWarnings("unchecked")    public void callbackTest() throws Exception {        List<String> list = mock(List.class);        list.add("hello");        when(list.get(0)).thenAnswer(new Answer<String>() {            @Override            public String answer(InvocationOnMock invocationOnMock) throws Throwable {                //获得函数调用的参数                Object[] args = invocationOnMock.getArguments();                //获得Mock对象本身                Object mock = invocationOnMock.getMock();                return "called with arguments:" + args[0].toString();            }        });        System.out.println(list.get(0));    }}

十三、监控真实对象

你可以为真实对象创建一个监控(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并不会为真实对象代理函数调用,实际上它会复制真实对象。所以,如果你保留了真实对象并与其交互,不要期望从监控对象得到正确的结果。当你在监控对象上调用一个没有被打桩的函数时,并不会调用真实对象的对应函数,因此你不会在真实对象上看到任何结果。

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


原创粉丝点击