什么是 Mock 测试
Mock 测试就是在测试过程中,对于某些不容易构造(如 HttpServletRequest 必须在Servlet 容器中才能构造出来)或者不容易获取比较复杂的对象(如 JDBC 中的ResultSet 对象),用一个虚拟的对象(Mock 对象)来创建以便测试的测试方法。
Mock 最大的功能是帮你把单元测试的耦合分解开,如果你的代码对另一个类或者接口有依赖,它能够帮你模拟这些依赖,并帮你验证所调用的依赖的行为。
比如一段代码有这样的依赖:
当我们需要测试A类的时候,如果没有 Mock,则我们需要把整个依赖树都构建出来,而使用 Mock 的话就可以将结构分解开,像下面这样:
Mock 对象使用范畴
真实对象具有不可确定的行为,产生不可预测的效果(如:股票行情,天气预报) :
- 真实对象很难被创建的
- 真实对象的某些行为很难被触发
真实对象实际上还不存在的(和其他开发小组或者和新的硬件打交道)等等
使用 Mock 对象测试的关键步骤
使用一个接口来描述这个对象
- 在产品代码中实现这个接口
- 在测试代码中实现这个接口
在被测试代码中只是通过接口来引用对象,所以它不知道这个引用的对象是真实对象,还是 Mock 对象。
Mock 与 Stub 的区别
Mock 不是 Stub,两者是有区别的:
前者被称为 mockist TDD,而后者一般称为 classic TDD ;
- 前者是基于行为的验证(behavior verification),后者是基于状态的验证 (state verification);
-
Java Mock 测试
目前,在 Java 阵营中主要的 Mock 测试工具有 Mockito,JMock,EasyMock 等。
关于这些框架的比较,不是本文的重点。本文着重介绍 Mockito 的使用。Mockito 的特性
Mockito 是美味的 Java 单元测试 Mock 框架,开源。
大多 Java Mock 库如 EasyMock 或 JMock 都是 expect-run-verify (期望-运行-验证)方式,而 Mockito 则使用更简单,更直观的方法:在执行后的互动中提问。使用 Mockito,你可以验证任何你想要的。而那些使用 expect-run-verify 方式的库,你常常被迫查看无关的交互。
非 expect-run-verify 方式 也意味着,Mockito 无需准备昂贵的前期启动。他们的目标是透明的,让开发人员专注于测试选定的行为。
Mockito 拥有的非常少的 API,所有开始使用 Mockito,几乎没有时间成本。因为只有一种创造 mock 的方式。只要记住,在执行前 stub,而后在交互中验证。你很快就会发现这样 TDD java 代码是多么自然。
类似 EasyMock 的语法来的,所以你可以放心地重构。Mockito 并不需要“expectation(期望)”的概念。只有 stub 和验证。
Mockito 实现了 Gerard Meszaros 所谓的 Test Spy.其他的一些特点:
可以 mock 具体类而不单止是接口
- 一点注解语法糖 - @Mock
- 干净的验证错误是 - 点击堆栈跟踪,看看在测试中的失败验证;点击异常的原因来导航到代码中的实际互动。堆栈跟踪总是干干净净。
- 允许灵活有序的验证(例如:你任意有序 verify,而不是每一个单独的交互)
- 支持“详细的用户号码的时间”以及“至少一次”验证
- 灵活的验证或使用参数匹配器的 stub (anyObject(),anyString() 或 refEq() 用于基于反射的相等匹配)
允许创建自定义的参数匹配器或者使用现有的 hamcrest 匹配器
IDEA 中如何使用Mockito框架
Mockito 语法流程
Mockito
Mock
mock(Class classToMock);
mock(Class classToMock, String name)
Stub
when(mock.someMethod()).thenReturn(value)
when(mock.someMethod()).thenThrow(new RuntimeException)
when(mock.someMethod()).thenAnswer()
exec
首先要利用mock来构造依赖,其次利用when语句来构造stub,然后就可以执行测试方法了
如何使用Mockito
- 在Intellij IDEA中创建Maven项目
- 在pom.xml添加依赖包
接下来就是创建测试类在src/test/java 目录里 ```java package com.portal.mock;<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.15.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
import org.testng.annotations.Test;
import java.util.LinkedList; import java.util.List;
import static org.mockito.Mockito.*;
public class MockTest {
@Test
public void testA(){
//You can mock concrete classes, not just interfaces
LinkedList mockList=mock(LinkedList.class);
//using mock
mockList.add("once");
mockList.add("twice");
mockList.add("twice");
mockList.add("three times");
mockList.add("three times");
mockList.add("three times");
//following two verifications work exactly the same - times(1) is used by default
verify(mockList).add("once");
verify(mockList,times(1)).add("once");
//exact number of invocations verification
verify(mockList, times(2)).add("twice");
verify(mockList,times(3)).add("three times");
//verification using never(). never() is an alias to times(0)
verify(mockList,never()).add("never happend");
//verification using atLeast()/atMost()
verify(mockList,atLeastOnce()).add("three times");
verify(mockList,atLeast(2)).add("three times");
verify(mockList,atMost(5)).add("three times");
}
@Test
public void testB(){
List list = new LinkedList();
List spy = spy(list);
//optionally, you can stub out some methods:
when(spy.size()).thenReturn(100);
//using the spy calls *real* methods
spy.add("zero"); //index从0开始
spy.add("one");
spy.add(2,"three"); //指定index
//prints "one" - the first element of a list
System.out.println("spy.index0: "+spy.get(0));
System.out.println("spy.index1: "+spy.get(1));
System.out.println("spy.index2: "+spy.get(2));
//size() method was stubbed - 100 is printed
System.out.println("Spy.size: "+ spy.size());
//optionally, you can verify
verify(spy).add("zero");
verify(spy).add("one");
verify(spy).add(2,"three");
}
}
看下执行结果:<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/22478710/1632674498491-24ac9933-22e8-4593-a5ab-06ddf04cb4ee.png#clientId=ue434ae4b-7da6-4&from=paste&id=u66148639&margin=%5Bobject%20Object%5D&name=image.png&originHeight=416&originWidth=905&originalType=url&ratio=1&size=177365&status=done&style=none&taskId=ua1ada28e-d157-4775-855e-8eb79fd210d)<br />我们把验证结果修改下,看会发生什么
```java
@Test
public void testB(){
List list = new LinkedList();
List spy = spy(list);
//optionally, you can stub out some methods:
when(spy.size()).thenReturn(100);
//using the spy calls *real* methods
spy.add("zero"); //默认index从0开始
spy.add("one");
spy.add(2,"three"); //指定index
//prints "one" - the first element of a list
System.out.println("spy.index0: "+spy.get(0));
System.out.println("spy.index1: "+spy.get(1));
System.out.println("spy.index2: "+spy.get(2));
//size() method was stubbed - 100 is printed
System.out.println("Spy.size: "+ spy.size());
//optionally, you can verify
verify(spy).add("zero");
verify(spy).add("one");
verify(spy).add(2,"two"); //修改验证结果 three -> two
}
Mockito语法相对来说还是比较简练的,学习成本并不算太高.
如果你有好的工具或者框架欢迎推荐给我, 一起学习一起进步.
总结:
测试是一门技术, 更是一门艺术. 也许你今天拥有的技术, 明天就会被淘汰. 同时需要我们开拓思维和眼界, 积极拥抱变化, 学习新知识, 新方法,新技能, 计算机领域讲究的是实践, 学习更要讲究方式方法. 学习和动手一定要结合, 光看不练,犹如看武功秘籍, 是永远成不了武功大侠的.