什么是 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 语法流程
MockitoMockmock(Class classToMock);mock(Class classToMock, String name)Stubwhen(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 {
@Testpublic void testA(){//You can mock concrete classes, not just interfacesLinkedList mockList=mock(LinkedList.class);//using mockmockList.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 defaultverify(mockList).add("once");verify(mockList,times(1)).add("once");//exact number of invocations verificationverify(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");}@Testpublic 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* methodsspy.add("zero"); //index从0开始spy.add("one");spy.add(2,"three"); //指定index//prints "one" - the first element of a listSystem.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 printedSystem.out.println("Spy.size: "+ spy.size());//optionally, you can verifyverify(spy).add("zero");verify(spy).add("one");verify(spy).add(2,"three");}
}
看下执行结果:<br /><br />我们把验证结果修改下,看会发生什么```java@Testpublic 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* methodsspy.add("zero"); //默认index从0开始spy.add("one");spy.add(2,"three"); //指定index//prints "one" - the first element of a listSystem.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 printedSystem.out.println("Spy.size: "+ spy.size());//optionally, you can verifyverify(spy).add("zero");verify(spy).add("one");verify(spy).add(2,"two"); //修改验证结果 three -> two}

Mockito语法相对来说还是比较简练的,学习成本并不算太高.
如果你有好的工具或者框架欢迎推荐给我, 一起学习一起进步.
总结:
测试是一门技术, 更是一门艺术. 也许你今天拥有的技术, 明天就会被淘汰. 同时需要我们开拓思维和眼界, 积极拥抱变化, 学习新知识, 新方法,新技能, 计算机领域讲究的是实践, 学习更要讲究方式方法. 学习和动手一定要结合, 光看不练,犹如看武功秘籍, 是永远成不了武功大侠的.
