Hello 大家好,我是阿粉,日常工作中很多时候我们都需要同事间的相互配合协作完成某些功能,所以我们经常会遇到服务或者应用内不同模块之间要互相依赖的场景。比如下面的场景,
serviceA
中的
methodA()
方式依赖
serviceB
中的
methodB()
方法返回操作的结果。那如果我们要对自己的
methodA()
方法进行编写单元测试,还需要等其他同事的
methodB()
方法开发完成才行。那有没有什么办法我们可以跳过或者说模拟方法 B 的输出呢?这就引出了我们今天的主角
Mockito
,一个优秀的
Mock
测试框架。
我们通过使用
Mock
技术可以让开发不停滞,
Mock
技术的作用是将服务与服务之间的依赖在测试自测阶段隔离开,让开发人员在自己的应用内部通过模拟的方式把需要依赖外部的接口给构造出来,从而保证不被外界的开发进度所影响。今天我们要谈到的
Mockito
就是一个优秀的
Mock
框架。
Mockito
Mockito is a mocking framework that tastes really good. It lets you write beautiful tests with a clean & simple API. Mockito doesn’t give you hangover because the tests are very readable and they produce clean verification errors.
Mockito
是一个很好用的模拟框架。它让您可以使用干净简单的
API
编写漂亮的测试。
Mockito
的可读性非常好,不会让你感动迷惑,产生的验证错误也很明确。
官网地址:
https://site.mockito.org/
中文文档:
https://github.com/hehonghui/mockito-doc-zh#0
测试用例 1
首先在工程的
pom
文件里面加依赖,我们加上
mockito
和
junit
的依赖。
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.9.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
接下来我们编写一个简单的测试用例,这里我们通过
mock
一个
List
对象,先添加几个元素,后面验证添加交互是否与我们预期的一致。
@Test
public void testVerify() throws Exception {
//创建 mock 对象
List mockedList = mock(List.class);
mockedList.add("test1");
mockedList.add("test2");
mockedList.add("test2");
mockedList.clear();
//验证是否执行了一次 add("test1") 操作
verify(mockedList).add("test1");
//同上面验证是否执行了一次 add("test1") 操作,默认就是 time(1)
verify(mockedList, times(1)).add("test1");
//验证是否执行了3次 add("test2") 操作
//verify(mockedList, times(3)).add("test2");
verify(mockedList).clear();
}
上面的测试用例我们运行过后是如下效果,测试用例是通过的。
当我们放开
verify(mockedList, times(3)).add("test2");
这一行代码进行运行时,我们可以看到测试用例未通过,提示的错误是我们预期执行 3 次,结果实际只执行了 2 次
add("test2")
操作。
上面的测试用例是验证对应方式的执行次数是否和预期一致,除了有准确的次数之外,还有最多,至少,从未等验证方式,如下所示:
//精确次数
verify(mockedList, times(3)).add("test2");
//至少 1次
verify(mockedList, atLeastOnce()).add("test2");
//至少 2 次
verify(mockedList, atLeast(2)).add("test2");
//最多 5 次
verify(mockedList, atMost(5)).add("test2");
测试用例 2
通过设值或者打桩的方式预设参数,如下所示,当执行
get(0)
操作时,我们通过
thenReturn()
方法返回
hello
,当执行
get(1)
操作时我们抛出空指针异常,运行结果如下图所示:
@Test
public void testWhen() throws Exception {
LinkedList mockedList = mock(LinkedList.class);
//设置值,通常被称为打桩
when(mockedList.get(0)).thenReturn("hello");
when(mockedList.get(1)).thenThrow(new NullPointerException());
System.out.println(mockedList.get(0));
//这里会打印 "null" 因为 get(2) 没有设置
System.out.println(mockedList.get(2));
//这里会抛 exception
System.out.println(mockedList.get(1));
//验证有没有执行 get(0) 操作
verify(mockedList).get(0);
}
可以看到当我们调用
get(0)
和
get(1)
的时候控制台成功的抛出了异常。这种方式通常被称为
Stubbing
,除了使用
when...thenReturn
方式之外,还有一种形式可以表达,代码如下:
@Test
public void testDoReturn() throws Exception {
Iterator mockedList = mock(Iterator.class);
doReturn("hello").when(mockedList).next();
Object next = mockedList.next();
System.out.println(next);
doReturn("world").when(mockedList).next();
Object next2 = mockedList.next();
System.out.println(next2);
//上面的过程也可以写成如下方式
doReturn("test1", "test2").when(mockedList).next();
Object next3 = mockedList.next();
System.out.println(next3);
Object next4 = mockedList.next();
System.out.println(next4);
}
运行结果如下所示,也可以用
doThrow()
方法进行抛异常:
测试用例 3
日常开发中我们通过要保证方法的时效性,或者说我们要保证我们某个方法必须在多长时间内执行完成,这个时候我们也可以通过 mock 的方式来验证我们的方法是否满足要求。代码如下:
@Test
public void testTimeout() throws Exception {
HttpService mock = mock(HttpService.class);
String url = "http://www.xxx.com";
mock.getRequest(url);
verify(mock, timeout(100)).getRequest(url);
//timeout时间后,用自定义的检验模式验证getRequest()
VerificationMode customVer = new VerificationMode() {
@Override
public void verify(VerificationData data) {
}
@Override
public VerificationMode description(String s) {
return null;
}
};
verify(mock, new Timeout(100, customVer)).getRequest(url);
}
Mockito 还有很多 API 可以使用,更多的使用方式,大家可以参考这面这个网站。
https://www.tutorialspoint.com/mockito/mockito_timeouts.htm
,有更详细的介绍。