标签:
本文中API文档部分,翻译自:Mockito
水平有限自己感觉很多地方表达的并不到位,但找不到更好的表达方式,如果您觉着有更好的表达方式,帮助我改进!
Mockito 是一个开源的Java测试框架,它能够Mock对象、验证结果以及为测试用例打桩。
Mockito 在运行时Mock对象,并且模拟被测试对象的行为,从而达到消除依赖的效果。
下面的例子mock一个list,因为很多人已经熟悉接口(比如,add() , get() , clear()方法)。在实际开发中,请不要mock List类。使用一个真实的实例来代替。
//通过静态导入的方式,可以让代码看起来很干净
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对象一旦被创建后,它将会记住所有和它进行的交互。这样你就可以选择任何你感兴趣的交互来进行验证。
//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.
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()方法。
//使用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则表示从未调用,通过他们可以判断一个函数被调用的次数。
doThrow(new RuntimeException()).when(mockedList).clear();
//调用clear() 会抛出 RuntimeException()
mockedList.clear();
阅读本文第12章节的doTrow|doAnswer系列方法。最初,stubVoid(Object)被用来为void进行插桩。现在stubVoid()方法已经弃用了,推荐使用doThrow(Throwable…)。这是为了提高可读性和doAnswer(Answer)系列方法的一致性。
// 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传递进去,进行顺序验证。
//使用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);
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
有时我们需要为同一个方法调用的返回值或者异常做测试桩。典型的运用就是使用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");
为泛型接口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"));
为无返回值的方法打桩需要从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(),来对测试你的所有的测试桩调用
了解更多关于这些方法的信息:
你可以为真实对象创建一个监控(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并不会为真实对象代理函数调用,实际上它会复制真实对象。所以,如果你保留了真实对象并与其交互,不要期望从监控对象得到正确的结果。当你在监控对象上调用一个没有被打桩的函数时,并不会调用真实对象的对应函数,因此你不会在真实对象上看到任何结果。
因此结论就是:当你在监控一个真实对象时,你想为这个真实对象的函数做测试桩,那么就是在自找麻烦。或者你根本不应该验证这些函数。
本文中API文档部分,翻译自:Mockito
标签:
原文地址:http://blog.csdn.net/watermusicyes/article/details/51333151