java的mock测试框架
无论是敏捷开发、持续交付,还是测试驱动开发(TDD)都把单元测试作为实现的基石。随着这些先进的编程开发模式日益深入人心,单元测试如今显得越来越重要了。在敏捷开发、持续交付中要求单元测试一定要快(不能访问实际的文件系统或数据库),而TDD经常会碰到协同模块尚未开发的情况,而mock技术正是解决这些问题的灵丹妙药。
mock技术的目的和作用是模拟一些在应用中不容易构造或者比较复杂的对象,从而把测试与测试边界以外的对象隔离开。
我们可以自己编写自定义的Mock对象实现mock技术,但是编写自定义的Mock对象需要额外的编码工作,同时也可能引入错误。现在实现mock技术的优秀开源框架有很多,本文对几个典型的mock测试框架作了简明介绍,希望对大家有所帮助。
1.EasyMock
EasyMock 是早期比较流行的MocK测试框架。它提供对接口的模拟,能够通过录制、回放、检查三步来完成大体的测试过程,可以验证方法的调用种类、次数、顺序,可以令 Mock 对象返回指定的值或抛出指定异常。通过 EasyMock,我们可以方便的构造 Mock 对象从而使单元测试顺利进行。
EasyMock 是采用 MIT license 的一个开源项目,可以在 Sourceforge 上下载到。(http://sourceforge.net/projects/easymock/files/EasyMock/)
如果使用maven也可以如下引入:

<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<version>3.1</version>
<scope>test</scope>
</dependency>

使用EasyMock大致可以划分为以下几个步骤:
① 使用 EasyMock 生成 Mock 对象;
② 录制 Mock 对象的预期行为和输出;
③ 将 Mock 对象切换到 播放 状态;
④ 调用 Mock 对象方法进行单元测试;
⑤ 对 Mock 对象的行为进行验证。
现在用一个例子来简单呈现以上的步骤,假设有一个类需要被模拟的类如下:

public class Class1Mocked {
public String hello(String name){
System.out.println("hello "+name);
return "hello "+name;
}
public void show(){
System.out.println("Class1Mocked.show()");
}
}

首先静态导入EasyMock的方法:
import static org.easymock.EasyMock.*;
例1.1 EasyMock第一个例子

@Test
public void testMockMethod() {
Class1Mocked obj = createMock(Class1Mocked.class);① expect(obj.hello("z3")).andReturn("hello l4");②
replay(obj);③ String actual = obj.hello("z3");④
assertEquals("hello l4", actual); verify(obj);⑤
}

在⑤验证阶段中,会严格验证mock对象是否按录制的行为如期发生(包括执行的顺序及次数)。
2.mockito
EasyMock之后流行的mock工具。相对EasyMock学习成本低,而且具有非常简洁的API,测试代码的可读性很高。
mockito可以在https://code.google.com/p/mockito/上下载,如果使用maven可以如下引入:

<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.9.5</version>
<scope>test</scope>
</dependency>

使用mockito大致可以划分为以下几个步骤:
① 使用 mockito 生成 Mock 对象;
② 定义(并非录制) Mock 对象的行为和输出(expectations部分);
③ 调用 Mock 对象方法进行单元测试;
④ 对 Mock 对象的行为进行验证。
现在用一个例子来简单呈现以上的步骤:
首先静态导入mockito的方法:
import static org.mockito.Mockito.*;
例2.1 mockito第一个例子

@Test
public void testMockMethod() {
Class1Mocked obj=mock(Class1Mocked.class);① when(obj.hello("z3")).thenReturn("hello l4");② String actual=obj.hello("z3");③
assertEquals("hello l4",actual); verify(obj).hello("z3");④
//verify(obj,times(1)).hello("z3"); //可以加参数验证次数
}

可以看到与EasyMock相比,少了切换到播放状态一步。这是很自然的,本来就不是录制而谈播放呢,而在验证阶段可以通过增加参数(time(int)、atLeastOnce()、atLeast(int)、never()等)来精确验证调用次数。
而如果要验证调用顺序可以如下控制:
例2.2 验证顺序

@Test
public void testMockMethodInOrder() {
Class1Mocked objOther = mock(Class1Mocked.class);
Class1Mocked objCn = mock(Class1Mocked.class); when(objOther.hello("z3")).thenReturn("hello l4");
when(objCn.hello("z3")).thenReturn("hello 张三"); String other = objOther.hello("z3");
assertEquals("hello l4", other);
String cn = objCn.hello("z3");
assertEquals("hello 张三", cn); InOrder inOrder = inOrder(objOther, objCn); //此行并不决定顺序,下面的两行才开始验证顺序
inOrder.verify(objOther).hello("z3");
inOrder.verify(objCn).hello("z3");
}

在之前的介绍的模拟操作中,我们总是去模拟一整个类或者对象,对于没有使用 When().thenReturn()方法指定的函数,系统会返回各种类型的默认值(具体值可参考官方文档)。而局部模拟创建出来的模拟对象依然是原系统对象,虽然可以使用方法When().thenReturn()来指定某些具体方法的返回值,但是没有被用此函数修改过的函数依然按照系统原始类的方式来执行,下面对非局部模拟和局部模拟分别举例来说明:
例2.3 非局部模拟

@Test
public void testSkipExpect() {
Class1Mocked obj = mock(Class1Mocked.class); assertEquals(null, obj.hello("z3"));
obj.show(); verify(obj).hello("z3");
verify(obj).show();
}

上面的代码省略了expectations部分(即定义代码行为和输出),运行该测试可以看到hello方法默认返回null(show方法本来就是无返回值的),而且在控制台中两个方法都没有输出任何语句。
mockito的局部模拟有两种方式,一种是doCallRealMethod()方式,另一种是spy()方式。
例2.4 局部模拟doCallRealMethod ()方式

@Test
public void testCallRealMethod () {
Class1Mocked obj = mock(Class1Mocked.class); doCallRealMethod().when(obj).hello("z3"); assertEquals("hello z3",obj.hello("z3"));
assertEquals(null,obj.hello("l4"));
obj.show(); verify(obj).hello("z3");
verify(obj).hello("l4");
verify(obj).show();
}

运行这个测试会发现在执行hello("z3")时会执行原有的代码,而执行hello("l4")时则是返回默认值null且没有输出打印,执行show()同样没有输出打印。
例2.5 局部模拟spy()方式

@Test
public void testSpy() {
Class1Mocked obj = spy(new Class1Mocked()); doNothing().when(obj).show(); assertEquals("hello z3",obj.hello("z3"));
obj.show(); verify(obj).hello("z3");
verify(obj).show();
}

运行这个测试会发现在执行hello("z3")时会执行原有的代码,但是执行show()时在控制台中没有打印语句。
但值得注意的是在mockito的psy()方式模拟中expectations部分使用的语法不同,执行起来存在微妙的不同,如下:
例2.6 值得注意的“陷阱”

@Test
public void testSpy2() {
Class1Mocked obj = spy(new Class1Mocked()); when(obj.hello("z3")).thenReturn("hello l4"); assertEquals("hello l4",obj.hello("z3")); verify(obj).hello("z3");
}

上面的代码虽然能顺利运行,但在控制台中输出了hello z3,说明实际的代码仍然执行了,只是mockito在最后替换了返回值。但下面的代码就不会执行实际的代码:

@Test
public void testSpy3() {
Class1Mocked obj = spy(new Class1Mocked()); doReturn("hello l4").when(obj).hello("z3"); assertEquals("hello l4",obj.hello("z3")); verify(obj).hello("z3");
}

3.PowerMock
这个工具是在EasyMock和Mockito上扩展出来的,目的是为了解决EasyMock和Mockito不能解决的问题,比如对static, final, private方法均不能mock。其实测试架构设计良好的代码,一般并不需要这些功能,但如果是在已有项目上增加单元测试,老代码有问题且不能改时,就不得不使用这些功能了。
PowerMock 在扩展功能时完全采用和被扩展的框架相同的 API, 熟悉 PowerMock 所支持的模拟框架的开发者会发现 PowerMock 非常容易上手。PowerMock 的目的就是在当前已经被大家所熟悉的接口上通过添加极少的方法和注释来实现额外的功能。目前PowerMock 仅扩展了 EasyMock 和 mockito,需要和EasyMock或Mockito配合一起使用。
PowerMock可以在https://code.google.com/p/powermock/上下载,本文以PowerMock+mockito为例,使用maven的话,添加如下依赖即可,maven会自动引入mockito的包。

<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>1.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>1.5</version>
<scope>test</scope>
</dependency>

现在举例来说明PowerMock的使用,假设有一个类需要被模拟的类如下:

public class Class2Mocked {
public static int getDouble(int i){
return i*2;
}
public String getTripleString(int i){
return multiply3(i)+"";
}
private int multiply3(int i){
return i*3;
}
}

首先静态导入PowerMock的方法:
import static org.powermock.api.mockito.PowerMockito.*;
然后在使用junit4的测试类上做如下声明:
@RunWith(PowerMockRunner.class)
@PrepareForTest( { Class2Mocked.class })
例3.1 模拟静态方法

@Test
public void testMockStaticMethod() {
mockStatic(Class2Mocked.class);
when(Class2Mocked.getDouble(1)).thenReturn(3); int actual = Class2Mocked.getDouble(1);
assertEquals(3, actual); verifyStatic();
Class2Mocked.getDouble(1);
}

PowerMockit的局域模拟使用方式和mockito类似(毕竟是扩展mockito),但强大之处在于可以模拟private方法,普通方法和final方法。模拟普通方法和final方法的方式与模拟private方法一模一样,现以模拟private方法为例。
例3.2 模拟私有方法(doCallRealMethod方式)

@Test
public void testMockPrivateMethod() throws Exception {
Class2Mocked obj = mock(Class2Mocked.class); when(obj, "multiply3", 1).thenReturn(4);
doCallRealMethod().when(obj).getTripleString(1); String actual = obj.getTripleString(1);
assertEquals("4", actual); verifyPrivate(obj).invoke("multiply3", 1);
}

例3.3 模拟私有方法(spy方式)

@Test
public void testMockPrivateMethod2() throws Exception {
Class2Mocked obj = spy(new Class2Mocked());
when(obj, "multiply3", 1).thenReturn(4); String actual = obj.getTripleString(1);
assertEquals("4", actual); verifyPrivate(obj).invoke("multiply3", 1);
}

除此之外,PowerMock也可以模拟构造方法,如下所示:
例3.4 模拟构造方法

@Test
public void testStructureWhenPathDoesntExist() throws Exception {
final String directoryPath = "mocked path"; File directoryMock = mock(File.class); whenNew(File.class).withArguments(directoryPath).thenReturn(directoryMock);
when(directoryMock.exists()).thenReturn(true); File file=new File(directoryPath);
assertTrue(file.exists()); verifyNew(File.class).withArguments(directoryPath);
verifyPrivate(directoryMock).invoke("exists");
}

4.Jmockit
JMockit 是一个轻量级的mock框架是用以帮助开发人员编写测试程序的一组工具和API,该项目完全基于 Java 5 SE 的 java.lang.instrument 包开发,内部使用 ASM 库来修改Java的Bytecode。
Jmockit功能和PowerMock类似,某些功能甚至更为强大,但个人感觉其代码的可读性并不强。
Jmockit可以在https://code.google.com/p/jmockit/上下载,使用maven的话添加如下依赖即可:

<dependency>
<groupId>com.googlecode.jmockit</groupId>
<artifactId>jmockit</artifactId>
<version>1.0</version>
<scope>test</scope>
</dependency>

Jmockit也可以分类为非局部模拟与局部模拟,区分在于Expectations块是否有参数,有参数的是局部模拟,反之是非局部模拟。而Expectations块一般由Expectations类和NonStrictExpectations类定义。用Expectations类定义的,则mock对象在运行时只能按照 Expectations块中定义的顺序依次调用方法,不能多调用也不能少调用,所以可以省略掉Verifications块;而用NonStrictExpectations类定义的,则没有这些限制,所以如果需要验证,则要添加Verifications块。
现在举例说明Jmockit的用法:
例4.1 非局部模拟Expectations类定义

@Mocked //用@Mocked标注的对象,不需要赋值,jmockit自动mock
Class1Mocked obj; @Test
public void testMockNormalMethod1() {
new Expectations() {
{
obj.hello("z3");
returns("hello l4", "hello w5");
obj.hello("张三");
result="hello 李四";
}
}; assertEquals("hello l4", obj.hello("z3"));
assertEquals("hello w5", obj.hello("z3"));
assertEquals("hello 李四", obj.hello("张三")); try {
obj.hello("z3");
} catch (Throwable e) {
System.out.println("第三次调用hello(\"z3\")会抛出异常");
}
try {
obj.show();
} catch (Throwable e) {
System.out.println("调用没有在Expectations块中定义的方法show()会抛出异常");
}
}

例4.2 非局部模拟 NonStrictExpectations类定义

public void testMockNormalMethod2() {
new NonStrictExpectations() {
{
obj.hello("z3");
returns("hello l4", "hello w5");
}
}; assertEquals("hello l4", obj.hello("z3"));
assertEquals("hello w5", obj.hello("z3"));
assertEquals("hello w5", obj.hello("z3"));// 会返回在NonStrictExpectations块中定义的最后一个返回值
obj.show(); new Verifications() {
{
obj.hello("z3");
times = 3;
obj.show();
times = 1;
}
};
}

运行这个测试会发现show()方法没有在控制台输出打印语句,说明是Jmockit对show方法也进行了默认mock。
例4.3 局部模拟

@Test
public void testMockNormalMethod() throws IOException {
final Class1Mocked obj = new Class1Mocked();//也可以不用@Mocked标注,但需要final关键字
new NonStrictExpectations(obj) {
{
obj.hello("z3");
result = "hello l4";
}
}; assertEquals("hello l4", obj.hello("z3"));
assertEquals("hello 张三", obj.hello("张三")); new Verifications() {
{
obj.hello("z3");
times = 1;
obj.hello("张三");
times = 1;
}
};
}

运行这个测试发现hello("z3")返回由Expectations块定义的值,但hello("张三")执行的是实际的代码。
例4.4 模拟静态方法

@Test
public void testMockStaticMethod() {
new NonStrictExpectations(Class2Mocked.class) {
{
Class2Mocked.getDouble(1);
result = 3;
}
}; assertEquals(3, Class2Mocked.getDouble(1)); new Verifications() {
{
Class2Mocked.getDouble(1);
times = 1;
}
};
}

例4.5 模拟私有方法

@Test
public void testMockPrivateMethod() throws Exception {
final Class2Mocked obj = new Class2Mocked();
new NonStrictExpectations(obj) {
{
this.invoke(obj, "multiply3", 1);
result = 4;
}
}; String actual = obj.getTripleString(1);
assertEquals("4", actual); new Verifications() {
{
this.invoke(obj, "multiply3", 1);
times = 1;
}
};
}

例4.6 设置私有属性的值
假设有一个类需要被模拟的类如下:

public class Class3Mocked {
private String name = "name_init"; public String getName() {
return name;
} private static String className="Class3Mocked_init"; public static String getClassName(){
return className;
} public static int getDouble(int i){
return i*2;
} public int getTriple(int i){
return i*3;
}
}

如下可以设置私有属性的值:

@Test
public void testMockPrivateProperty() throws IOException {
final Class3Mocked obj = new Class3Mocked();
new NonStrictExpectations(obj) {
{
this.setField(obj, "name", "name has bean change!");
}
}; assertEquals("name has bean change!", obj.getName());
}

例4.7 设置静态私有属性的值

@Test
public void testMockPrivateStaticProperty() throws IOException {
new NonStrictExpectations(Class3Mocked.class) {
{
this.setField(Class3Mocked.class, "className", "className has bean change!");
}
}; assertEquals("className has bean change!", Class3Mocked.getClassName());
}

例4.8 改写普通方法的内容

@Test
public void testMockNormalMethodContent() throws IOException {
final Class3Mocked obj = new Class3Mocked();
new NonStrictExpectations(obj) {
{
new MockUp<Class3Mocked>() {
@Mock
public int getTriple(int i) {
return i * 30;
}
};
}
}; assertEquals(30, obj.getTriple(1));
assertEquals(60, obj.getTriple(2));
}

例4.9 改写静态方法的内容
如果要改写Class3Mocked类的静态getDouble方法,则需要新建一个类含有与getDouble方法相同的函数声明,并且用@Mock标注,如下:

public class Class4Mocked {
@Mock
public static int getDouble(int i){
return i*20;
}
}

如下即可改写:

@Test
public void testDynamicMockStaticMethodContent() throws IOException {
Mockit.setUpMock(Class3Mocked.class, Class4Mocked.class); assertEquals(20, Class3Mocked.getDouble(1));
assertEquals(40, Class3Mocked.getDouble(2));
}

java的mock测试框架的更多相关文章
- Mock测试框架(Mockito为例)
在做单元测试的时候,有的时候用到的一些类,我们构造起来不是那么容易,比如HttpRequest,或者说某个Service依赖到了某个Dao,想构造service还得先构造dao,这些外部对象构造起来比 ...
- mock测试框架Mockito
无论是敏捷开发.持续交付,还是测试驱动开发(TDD)都把单元测试作为实现的基石.随着这些先进的编程开发模式日益深入人心,单元测试如今显得越来越重要了.在敏捷开发.持续交付中要求单元测试一定要快(不能访 ...
- Selenium 4 Java的最佳测试框架
几十年来,Java一直是开发应用程序服务器端的首选编程语言.尽管JUnit一直在与开发人员一起帮助他们进行自动化的单元测试,但随着时间的推移和测试行业的发展,特别是伴随着自动化测试的兴起,已经开发了许 ...
- Mock测试框架
一.前言 使用Mock框架进行单元测试,能够使用当前系统已经开发的接口方法模拟数据.(未写完,慢慢完善) 二.例子 1.引用Moq
- Java高并发测试框架JCStress
前言 如果要研究高并发,一般会借助高并发工具来进行测试.JCStress(Java Concurrency Stress)它是OpenJDK中的一个高并发测试工具,它可以帮助我们研究在高并发场景下JV ...
- unittest单元测试,基于java的junit测试框架
import unittestclass study(unittest.TestCase): def testXia(self): self.assertEqual((3*4),20) def tes ...
- Java Junit测试框架
Java Junit测试框架 1.相关概念 Ø JUnit:是一个开发源代码的Java测试框架,用于编写和运行可重复的测试.它是用于单元测试框架体系xUnit的一个实例(用于java语言).主要 ...
- Mockito:一个强大的用于Java开发的模拟测试框架
https://blog.csdn.net/zhoudaxia/article/details/33056093 介绍 本文将介绍模拟测试框架Mockito的一些基础概念, 介绍该框架的优点,讲解应用 ...
- 【转】Java学习---10个测试框架介绍
[原文]https://www.toutiao.com/i6594302925458113027/ JAVA 程序员需要用到 10 个测试框架和库 Java 程序员需要用到十大单元测试和自动化集成测试 ...
随机推荐
- Kmeans 聚类 及其python实现
主要参考 K-means 聚类算法及 python 代码实现 还有 <机器学习实战> 这本书,当然前面那个链接的也是参考这本书,懂原理,会用就行了. 1.概述 K-means ...
- Storm介绍及核心组件和编程模型
离线计算 离线计算:批量获取数据.批量传输数据.周期性批量计算数据.数据展示 代表技术:Sqoop批量导入数据.HDFS批量存储数据.MapReduce批量计算数据.Hive批量计算数据.azkaba ...
- java集合类TreeMap和TreeSet
看这篇博客前,可以先看下下列这几篇博客 Red-Black Trees(红黑树) (TreeMap底层的实现就是用的红黑 ...
- 002.LVM创建
一 LVM创建步骤 创建分区 创建PV 创建VG 创建LV 格式化及挂载 二 创建分区 使用分区工具(如fdisk等)创建LVM分区,却将分区标识为LVM的分区类型8e. [root@kauai ~] ...
- 机器学习入门 一、理解机器学习+简单感知机(JAVA实现)
首先先来讲讲闲话 如果让你现在去搞机器学习,你会去吗?不会的话是因为你对这方面不感兴趣,还是因为你觉得这东西太难了,自己肯定学不来?如果你觉的太难了,很好,相信看完这篇文章,你就会有胆量踏入机器学习这 ...
- HDU5919 SequenceⅡ
从后向前建主席树,以位置为下标建树,然后查询区间出现次数的第k/2大即可. 复杂度O(nlogn) #include<bits/stdc++.h> using namespace std; ...
- FHQ Treap及其可持久化与朝鲜树式重构
FHQ Treap,又称无旋treap,一种不基于旋转机制的平衡树,可支持所有有旋treap.splay等能支持的操作(只有在LCT中会比splay复杂度多一个log).最重要的是,它是OI中唯一一种 ...
- hihoCoder.1457.后缀自动机四 重复旋律7(广义后缀自动机)
题目链接 假设我们知道一个节点表示的子串的和sum,表示的串的个数cnt,那么它会给向数字x转移的节点p贡献 \(sum\times 10+c\times cnt\) 的和. 建广义SAM,按拓扑序正 ...
- [Java]如何把当前时间插入到数据库
[Java]如何把当前时间插入到数据库 1.在orderDao.java中 /** 设置订单*/ public void setOrder(Order order){ Date time = new ...
- Intel Code Challenge Final Round (Div. 1 + Div. 2, Combined) A. Checking the Calendar 水题
A. Checking the Calendar 题目连接: http://codeforces.com/contest/724/problem/A Description You are given ...