Mockito 测试基本用法


一、什么是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);

九、查找多余的调用

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

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



注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
© 2014-2019 ITdaan.com 粤ICP备14056181号