一、 mock介绍
1.1简介
mock测试
就是在测试过程中,对于某些不容易构造或者 不容易获取的对象,用一个虚拟的对象来创建以便测试的测试方法。
mock对象
这个虚拟的对象就是mock对象。mock对象就是真实对象在调试期间的代替品。
mock对象使用范畴
真实对象具有不可确定的行为,产生不可预测的效果,(如:股票行情,天气预报) 真实对象很难被创建的 真实对象的某些行为很难被触发 真实对象实际上还不存在的(和其他开发小组或者和新的硬件打交道)等等.
什么时候需要Mock对象
----- 真实对象具有不可确定的行为(产生不可预测的结果,如股票的行情)
----- 真实对象很难被创建(比如具体的web容器)
----- 真实对象的某些行为很难触发(比如网络错误)
----- 真实情况令程序的运行速度很慢
----- 真实对象有用户界面
----- 测试需要询问真实对象它是如何被调用的(比如测试可能需要验证某个回调函数是否被调用了)
----- 真实对象实际上并不存在(当需要和其他开发小组,或者新的硬件系统打交道的时候,这是一个普遍的问题)
1.2举例说明
一个闹钟根据时间来进行提醒服务,如果过了下午5点钟就播放音频文件提醒大家下班了,如果我们要利用真实的对象来测试的话就只能苦苦等到下午五点,然后把耳朵放在音箱旁...我们可不想这么笨,我们应该利用 mock对象来进行测试,这样我们就可以模拟控制时间了,而不用苦苦等待时钟转到下午5点钟了。一个闹钟根据时间来进行提醒服务,如果过了下午5点钟就播 放音频文件提醒大家下班了,如果我们要利用真实的对象来测试的话就只能苦苦等到下午五点,然后把耳朵放在音箱旁...我们可不想这么笨,我们应该利用 mock对象来进行测试,这样我们就可以模拟控制时间了,而不用苦苦等待时钟转到下午5点钟了。
- public abstract class Environmental {
- boolean playedWav = false;
-
- public abstract long getTime();
-
- public abstract void playWavFile(String fileName);
-
- public abstract boolean wavWasPlayed();
-
- public abstract void resetWav();
- }
真实的实现代码:
- public class SystemEnvironment extends Environmental {
- public long getTime() {
- return System.currentTimeMillis();
- }
-
- public void playWavFile(String fileName) {
- playedWav = true;
- }
-
- public boolean wavWasPlayed() {
- return playedWav;
- }
-
- public void resetWav() {
- playedWav = false;
- }
- }
下面是mock对象:
- public class MockSystemEnvironment extends Environmental {
- private long currentTime;
-
- public long getTime() {
- return currentTime;
- }
-
- public void setTime(long currentTime) {
- this.currentTime = currentTime;
- }
-
- public void playWavFile(String fileName) {
- playedWav = true;
- }
-
- public boolean wavWasPlayed() {
- return playedWav;
- }
-
- public void resetWav() {
- playedWav = false;
- }
- }
下面是一个调用getTime的具体类:
- public class Checker {
- private Environmental env;
-
- public Checker(Environmental env) {
- this.env = env;
- }
-
- public void reminder() {
- Calendar cal = Calendar.getInstance();
- cal.setTimeInMillis(env.getTime());
- int hour = cal.get(Calendar.HOUR_OF_DAY);
- if (hour >= 17) {
- env.playWavFile("quit_whistle.wav");
- }
- }
- }
使用env.getTime()的被测代码并不知道测试环境和真实环境之间的区别,因为它们都实现了相同的接口。现在,你可以借助mock对象,通过把时间设置为已知值,并检查行为是否如预期那样来编写测试。
- public class TestChecker extends TestCase {
- public void testQuittingTime() {
- MockSystemEnvironment env = new MockSystemEnvironment();
- Calendar cal = Calendar.getInstance();
- cal.set(Calendar.YEAR, 2006);
- cal.set(Calendar.MONTH, 11);
- cal.set(Calendar.DAY_OF_MONTH, 7);
- cal.set(Calendar.HOUR_OF_DAY, 16);
- cal.set(Calendar.MINUTE, 55);
-
-
- long t1 = cal.getTimeInMillis();
- System.out.println(t1);
- env.setTime(t1);
- Checker checker = new Checker(env);
- checker.reminder();
- assertFalse(env.wavWasPlayed());
-
-
- t1 += (5 * 60 * 1000);
- System.out.println(t1);
- env.setTime(t1);
- checker.reminder();
- assertTrue(env.wavWasPlayed());
- env.resetWav();
-
-
- t1 += 2 * 60 * 60 * 1000;
- System.out.println(t1);
- env.setTime(t1);
- checker.reminder();
- assertTrue(env.wavWasPlayed());
- }
- }
这就是mock对象的全部:伪装出真实世界的某些行为,使你可以集中精力测试好自己的代码。
1.1Mock工具介绍
如果每次都像上面那样自己写具体的mock对象,问题虽然解决了,但是好像有一些麻烦。
已经有一些第三方现成的mock对象供我们使用了。
在Java阵营中主要的Mock测试工具有 JMock,MockCreator,Mockrunner,EasyMock,MockMaker等,在微软的.Net阵营中主要是 Nmock,.NetMock等。
一、 EasyMock介绍
2.1 简介
EasyMock是一种模拟测试的框架,用他来辅助模拟测试。当在测试过程中一些复杂的对象生成相当麻烦、费时或者根本无法生成时,可以用模拟的对象来代替真实的对象。
EasyMock 可以mock interface和java 类,但是class mocking是有一些限制的,
1) 不能mock类的 final方法
如果final方法被调用,则只能执行原有的正常代码。
2) 不能mock类的static 方法。
同样如果private方法被调用,只能执行原有的正常代码。
3) 不能mock类的一些特殊方法: equals(),toString()和hashCode().
原因是easymock在实现是为每个class mock对象提供了内建的以上三个方法。需要强调的是,对于基于interface的mock,这个限制也是同样存在的,即使以上三个方式是interface定义的一部分。
在使用时需要避开这种场景,或者组合使用其他的mock 框架比如jmockit来mock private方法和final方法
4) 3.0版本之前
org.easymock.classextension.EasyMock 被用来mock抽象类(abstract)和具体类
org.easymock.EasyMock被用来mock接口(interface)
3.0版本之后
easymock class extension的class mocking功能已经无缝集成到easymock中,因此代码的编写简洁了很多。
现新版本是3.2
2.2 EasyMock的安装
EasyMock 是采用 MIT license的一个开源项目,您可以http://www.easymock.org/Downloads.html上下载到相关的 zip 文件。
如果使用 Eclipse 作为 IDE,把 easymock.jar 添加到项目的 Libraries 里就可以使用了(如下图所示)。此外,由于我们的测试用例运行在 JUnit 环境中,因此您还需要 JUnit.jar(版本3.8.1以上)。
步骤:
解压EasyMock zip文件(easymock.zip).
添加EasyMock jar 文件(easymock.jar) 到你的工程路径.
为了运行mock程序,也需要添加 Objenesis 、Cglib 和 asm到你的工程路径.
图1:Eclipse 项目中的 Libraries
2.3 record-replay-verify模型介绍
record-replay-verify 模型容许记录mock对象上的操作然后重演并验证这些操作。这是目前mock框架领域最常见的模型,几乎所有的mock框架都是用这个模型,有些是现实使用如easymock,有些是隐式使用如jmockit。
这个过程大致可以划分为以下几个步骤:
1)record
使用 EasyMock 生成 Mock 对象;
设定 Mock 对象的预期行为和输出;
2)replay
将 Mock 对象切换到 Replay 状态;
3)verify
调用 Mock 对象方法进行单元测试;
对 Mock 对象的行为进行验证。
举例说明
- public interface MyTest {
- public boolean getBoolean();
- public int getInt(int i);
- public char getChar();
- public String getString();
- }
- public class MyTestTest {
-
- @Test
- public void test() {
-
- MyTest my=createMock(MyTest.class);
-
- expect(my.getBoolean()).andReturn(false);
- expect(my.getChar()).andReturn('b');
- expect(my.getString()).andReturn("world");
- expect(my.getInt(10)).andReturn(5);
-
- replay(my);
-
- assertEquals(5, my.getInt(10));
- assertEquals(false, my.getBoolean());
- assertEquals('b', my.getChar());
- assertEquals("world", my.getString());
-
- verify(my);
- }
- }
三、EasyMock各部分详解
3.1 record部分
3.1.1 使用 EasyMock生成 Mock 对象
一些简单的测试用例只需要一个 Mock对象,这时,我们可以用以下的方法来创建 Mock对象:
MyTest test1 = EasyMock.createMock(MyTest.class);
其中 createMock是 org.easymock.EasyMock类所提供的静态方法,你可以通过 static import将其引入(注:static import是 java 5.0 所提供的新特性)。
如果需要在相对复杂的测试用例中使用多个 Mock对象,EasyMock提供了另外一种生成和管理 Mock对象的机制:
EasyMock 类的 createControl方法能创建一个接口 IMocksControl的对象,该对象能创建并管理多个 Mock对象。如果需要在测试中使用多个 Mock对象,我们推荐您使用这一机制,因为它在多个 Mock对象的管理上提供了相对便捷的方法。
- public interface Test1 {
- public String getString();
- }
- public interface Test2 {
- public int getInt();
- }
- public class TestMock {
-
- IMocksControl im=createControl();
- Test1 t1=im.createMock(Test1.class);
- Test2 t2=im.createMock(Test2.class);
- @Test
- public void test() {
- expect(t1.getString()).andReturn("hi");
- expect(t2.getInt()).andReturn(2);
-
- im.replay();
-
- assertEquals(2, t2.getInt());
- assertEquals("hi", t1.getString());
-
- im.verify();
- }
- }
3.1.2 设定 Mock对象的预期行为和输出
Mock 对象的行为可以简单的理解为 Mock对象方法的调用和方法调用所产生的输出。在 EasyMock中,对 Mock对象行为的添加和设置是通过接口 IExpectationSetters来实现的。
IExpectationSetters<T> expect(final Tvalue);
IExpectationSetters<T> expectLastCall();
1) IExpectationSetters<T>expect(final T value);
对于被mock的方法参数value,EasyMock 提供了灵活的参数匹配方式。
(1) 基于基本类型的比较
eq(X value)
当实际值和预期值相同时匹配。适用于所有的简单类型和对象。
eq(X value, X delta)
当实际值在以value为基础以delta为区间的范围内匹配。适用于float和double。
anyBoolean(), anyByte(), anyChar(), anyDouble(), anyFloat(), anyInt(),anyLong(), anyObject(), anyShort()
匹配任意值。适用于所有的简单类型和对象。
aryEq(X value)
当通过Arrays.equals()比较返回true时匹配。适用于所有的简单类型和对象。
这个是eq(X value)方法的数组版本,要求比较的两个数组拥有相同的长度,然后每个元素都"相同",即都可以满足eq(X value)方法。
lt(X value), leq(X value), geq(X value), gt(X value)
当实际值小于、小于等于、大于等于、大于预期值时匹配。适用于所有数值简单类型。
(2) 基于对象的比较
eq(X value)
当实际值和预期值相同时匹配。和基本类型类似,不过对于Object,是通过调用equals()方法来进行比较。
same(X value)
当实际值和预期值是同一个对象引用时匹配。适用于对象。 和eq()不同,same()是通过比较对象引用来进行比较的。类似java代码中, a.equals(b)和a == b的差别。
anyObject() 和 anyObject(Class<T> clazz)
类似基本类型的any***()方法,非常宽松,在我们不介意参数值时使用。
isA(Class clazz)
匹配当前类或其子类的实例。适用于对象。 和anyObject(Class<T>clazz) 非常,唯一一个差别在于当输入参数为null时,anyObject(Class<T>clazz)返回true而isA(Class<T>clazz) 返回false。
(3) 逻辑计算
and(X first, X second)
当两个匹配器同时匹配时匹配。适用于所有的简单类型和对象。
or(X first, X second)
当两个匹配器有任何一个匹配时匹配。适用于所有的简单类型和对象。
not(X value)
当指定的匹配器不匹配时匹配。
此外在参数匹配中,有几个特殊角色,享受的待遇与众不同,easymock为它们提供了专有方法。
1. Comparable
对于实现了Comparable接口的对象,easymock提供了一系列的专用方法来处理,包括eq, gt, lt, geq, leq:
cmpEq(Comparable<T> value) gt(Comparable<T> value)lt(Comparable<T> value) geq(Comparable<T> value)leq(Comparable<T> value)
这个特殊处理非常合理,本来Comparable接口就提供了比较的功能,在参数匹配时应该容许直接使用。
2. string
由于字符串匹配使用的场景非常多,因此easymock为此也提供了几个常见的参数匹配方法:
contains(String substring) startsWith(String prefix) endsWith(String suffix)contains/startsWith/endsWith是简单的字符串查找,当实际值开始、中间、或结尾处含有期望值时匹配。适用于String类型。
matches(String regex)
find(String regex)
则通过支持正则表达式来提供复杂匹配。
3. null
isNull()
当实际值是null时匹配。适用于对象。
notNull()
当实际值不为null时匹配。适用于对象。
对于Object匹配,很常见的一个场景就是输入的参数为null,easymock中提供isNull()和 notNull() 两个方法来完成对null值的匹配。
开发中,经常会遇到下面这种场景,期望输入的参数满足isA()或者容许为null。而直接使用isA(),是不能支持null的,即如果参数为null时isA()会报不匹配。这个不是easymock的bug,而是刻意而为,解决的方法是使用 or(isA(...), isNull(...))或者anyObject()。
举例说明
- public interface MyTest {
- public int getInt(int i);
- public int getInt(int[] i);
- public int getInt(Object obj);
- public double getDouble(double d);
- public boolean getBoolean(long l);
-
- }
- public class MyTestTest {
- Father fa=new Father();
- Son son=new Son();
- Son son2=null;
- @Test
- public void test() {
- MyTest my=createMock(MyTest.class);
- int[] a={1,2,3,4,5};
- int[] b={1,2,3,4,5,6};
- int[] c={1,2,3,6,5};
- int[] d={1,2,3,4,5};
-
-
-
-
- expect(my.getInt(eq(10))).andReturn(10);
-
-
-
-
- expect(my.getDouble(eq(10, 2))).andReturn(5.0);
-
-
-
-
- expect(my.getBoolean(anyLong())).andReturn(true);
-
-
-
-
- expect(my.getInt(aryEq(a))).andReturn(100);
-
-
-
-
-
-
-
-
- expect(my.getInt(lt(20))).andReturn(1);
-
- expect(my.getInt(leq(20))).andReturn(2);
- expect(my.getInt(geq(20))).andReturn(3);
- expect(my.getInt(gt(20))).andReturn(4);
- expect(my.getInt(eq(20))).andReturn(5);
-
-
-
-
-
-
- expect(my.getInt(anyObject(Father.class))).andReturn(1000);
- expect(my.getInt(isA(Father.class))).andReturn(1001);
-
-
-
-
-
-
-
-
- expect(my.getInt(and(lt(5), gt(1)))).andReturn(200);
-
- replay(my);
-
- assertEquals(10, my.getInt(10));
- assertEquals(5.0,my.getDouble(11),0);
- assertEquals(true, my.getBoolean(25));
-
-
-
- assertEquals(100, my.getInt(d));
-
- assertEquals(1, my.getInt(19));
- assertEquals(2, my.getInt(20));
- assertEquals(3, my.getInt(20));
- assertEquals(4, my.getInt(21));
- assertEquals(5, my.getInt(20));
-
- assertEquals(1000, my.getInt(son2));
-
- assertEquals(1001, my.getInt(son));
-
- assertEquals(200, my.getInt(4));
-
- verify(my);
- }
-
- }
2)IExpectationSetters<T> expectLastCall();
如果mock对象的方法是void,则需要使用expectLastCall()
- public interface Test1 {
- public void method1() throws IOException;
- }
- public class Test2 {
-
- public int runTest1(Test1 t1) throws IOException{
- try{
- t1.method1();
- return 1;
- }catch(IOException e){
- return -1;
- }
- }
- }
- public class Test2Mock {
- Test2 t2=new Test2();
- Test1 t1=EasyMock.createMock(Test1.class);
- @Test
- public void test() throws IOException {
-
- t1.method1();
- EasyMock.expectLastCall();
- EasyMock.replay(t1);
- int a=t2.runTest1(t1);
- assertEquals(1, a);
- EasyMock.verify(t1);
-
- }
-
- }
3.1.3 设定输出
Mock 对象方法的调用可能产生两种类型的输出:(1)产生返回值;(2)抛出异常
接口 IExpectationSetters 提供了多种设定预期输出的方法
1)设定预期返回值
接口IExpectationSetters 和设定返回值相对应的是 andReturn 方法:
IExpectationSetters<T> andReturn(T value);
如:
expect(my.getInt(eq(10))).andReturn(10);
有时,我们希望某个方法的调用总是返回一个相同的值,为了避免每次调用都为 Mock 对象的行为进行一次设定,我们可以用设置默认返回值的方法:
void andStubReturn(Object value);
- public class MyTest {
-
- public int getInt(int i) {
- return 1;
- }
-
- }
- public class MyTestTest {
-
- @Test
- public void test() {
- MyTest my=createMock(MyTest.class);
-
-
- expect(my.getInt(10)).andReturn(5);
- expect(my.getInt(anyInt())).andStubReturn(1);
-
- replay(my);
-
-
- assertEquals(5, my.getInt(10));
- assertEquals(1, my.getInt(3));
-
- verify(my);
- }
-
- }
2)设定预期异常抛出
对象行为的预期输出除了可能是返回值外,还有可能是抛出异常。IExpectationSetters提供了设定预期抛出异常的方法:
IExpectationSetters<T> andThrow(Throwable throwable); 和设定默认返回值类似,IExpectationSetters 接口也提供了设定抛出默认异常的函数:
void andStubThrow(Throwable throwable);
非受检异常(也就是:RuntimeException,Error和它们的子类)能够在任何方法中被抛出。
受检异常只能在显示声明它们会被抛出的方法中被抛出。
- public interface Test1 {
- public void method1() throws IOException;
- }
- public class Test2 {
-
- public int runTest1(Test1 t1) throws IOException{
- try{
- t1.method1();
- return 1;
- }catch(IOException e){
- return -1;
- }
- }
- }
- public class Test2Mock {
- Test2 t2=new Test2();
- Test1 t1=EasyMock.createMock(Test1.class);
- @Test
- public void test() throws IOException {
-
- t1.method1();
- EasyMock.expectLastCall();
- EasyMock.replay(t1);
- int a=t2.runTest1(t1);
- assertEquals(1, a);
- EasyMock.verify(t1);
-
-
- EasyMock.reset(t1);
- t1.method1();
- EasyMock.expectLastCall().andThrow(new IOException());
- EasyMock.replay(t1);
- int b=t2.runTest1(t1);
- assertEquals(-1, b);
- EasyMock.verify(t1);
-
- }
-
- }
3.1.4 设定预期方法调用次数
除了对预期输出进行设定,IExpectationSetters接口还允许用户对方法的调用次数作出限制。在 IExpectationSetters 所提供的这一类方法中,常用的一种是 times方法:
IExpectationSetters<T>times(int count); 该方法可以 Mock对象方法的调用次数进行确切的设定。假设我们希望 mockResultSet 的 getString 方法在测试过程中被调用3次,期间的返回值都是 "My return value",我们可以用如下语句:
mockResultSet.getString(1);expectLastCall().andReturn("My return value").times(3);注意到 andReturn和andThrow 方法的返回值依然是一个 IExpectationSetters 实例,因此我们可以在此基础上继续调用 times 方法。
除了设定确定的调用次数,IExpectationSetters还提供了另外几种设定非准确调用次数的方法: times(int minTimes, int maxTimes):该方法最少被调用 minTimes 次,最多被调用 maxTimes 次。 atLeastOnce():该方法至少被调用一次。 anyTimes():该方法可以被调用任意次。
某些方法的返回值类型是 void,对于这一类方法,我们无需设定返回值,只要设置调用次数就可以了。以 ResultSet 接口的 close 方法为例,假设在测试过程中,该方法被调用3至5次:
mockResultSet.close(); expectLastCall().times(3,5); 为了简化书写,EasyMock还提供了另一种设定 Mock 对象行为的语句模式。对于上例,您还可以将它写成:
expect(mockResult.close()).times(3, 5);
举例:
- public interface MyTest {
- public int getInt(int i);
- }
- public class MyTestTime {
-
- @Test
- public void test() {
- MyTest my=createMock(MyTest.class);
-
-
- expect(my.getInt(10)).andReturn(2).times(2);
- expect(my.getInt(10)).andReturn(1).once();
- expect(my.getInt(10)).andReturn(4).times(3,5);
-
-
-
-
-
-
-
-
-
- replay(my);
-
-
- assertEquals(2, my.getInt(10));
- assertEquals(2, my.getInt(10));
- assertEquals(1, my.getInt(10));
- assertEquals(4, my.getInt(10));
- assertEquals(4, my.getInt(10));
- assertEquals(4, my.getInt(10));
- assertEquals(4, my.getInt(10));
- assertEquals(4, my.getInt(10));
- assertEquals(1, my.getInt(10));
- assertEquals(4, my.getInt(10));
- verify(my);
- }
-
- }
3.1.5 改变一个方法调用的行为
方法的调用次数、andReturn和andThrow 可以串联调用。
举例:
- public interface MyTest {
- public int getInt(int i);
- }
- public class MyTestTime {
-
- @Test
- public void test() {
- MyTest my=createMock(MyTest.class);
-
-
-
-
-
- replay(my);
-
-
- assertEquals(2, my.getInt(10));
- assertEquals(2, my.getInt(10));
- assertEquals(1, my.getInt(10));
- assertEquals(4, my.getInt(10));
- assertEquals(4, my.getInt(10));
- assertEquals(4, my.getInt(10));
- assertEquals(4, my.getInt(10));
- assertEquals(4, my.getInt(10));
- assertEquals(1, my.getInt(10));
- assertEquals(4, my.getInt(10));
- verify(my);
- }
-
- }
3.2 Replay部分
在生成 Mock 对象和设定Mock 对象行为两个阶段,Mock 对象的状态都是Record 。在这个阶段,Mock 对象会记录用户对预期行为和输出的设定。
在使用 Mock 对象进行实际的测试前,我们需要将 Mock 对象的状态切换为 Replay。在 Replay 状态,Mock对象能够根据设定对特定的方法调用作出预期的响应。
将 Mock 对象切换成 Replay 状态有两种方式,您需要根据 Mock 对象的生成方式进行选择。如果 Mock 对象是通过 org.easymock.EasyMock 类提供的静态方法 createMock 生成的(第1节中介绍的第一种 Mock 对象生成方法),那么EasyMock 类提供了相应的 replay 方法用于将 Mock 对象切换为 Replay 状态:
replay(mockResultSet);
如果 Mock 对象是通过 IMocksControl 接口提供的 createMock 方法生成的(第1节中介绍的第二种Mock对象生成方法),那么您依旧可以通过 IMocksControl 接口对它所创建的所有 Mock 对象进行切换:
control.replay();
3.3 Verify部分
在利用 Mock 对象进行实际的测试过程之后,我们还有一件事情没有做:对 Mock 对象的方法调用的次数进行验证。
为了验证指定的方法调用真的完成了,我们需要调用 verify 方法进行验证。如果Mock对象的方法没有被调用过,将会得到下面的异常:
java.lang.AssertionError: Expectation failure onverify:
和 replay 方法类似,您需要根据 Mock 对象的生成方式来选用不同的验证方式。如果 Mock 对象是由 org.easymock.EasyMock 类提供的 createMock 静态方法生成的,那么我们同样采用EasyMock 类的静态方法 verify 进行验证:
verify(mockResultSet); 如果Mock对象是有 IMocksControl 接口所提供的 createMock 方法生成的,那么采用该接口提供的 verify 方法,例如第1节中的IMocksControl实例 control:
control.verify();
3.4 对象重用
为了避免生成过多的 Mock
对象,EasyMock 允许对原有 Mock 对象进行重用。要对 Mock 对象重新初始化,我们可以采用 reset 方法。和 replay 和verify 方法类似,EasyMock 提供了两种reset 方式:(1)如果 Mock 对象是由 org.easymock.EasyMock 类中的静态方法 createMock 生成的,那么该 Mock 对象的可以用 EasyMock 类的静态方法 reset 重新初始化;(2)如果Mock 方法是由 IMocksControl 实例的createMock 方法生成的,那么该 IMocksControl 实例方法 reset 的调用将会把所有该实例创建的 Mock 对象重新初始化。
在重新初始化之后,Mock 对象的状态将被置为 Record 状态。
- public interfaceMyTest {
-
- public int getInt(int i);
- }
- public class MyTestTest {
-
- @Test
- public void test() {
- MyTest my=createMock(MyTest.class);
-
-
- expect(my.getInt(10)).andReturn(5);
- expect(my.getInt(anyInt())).andStubReturn(1);
-
- replay(my);
-
-
- assertEquals(5, my.getInt(10));
- assertEquals(1, my.getInt(3));
-
- verify(my);
-
-
-
-
- reset(my);
- expect(my.getInt(anyInt())).andStubReturn(1);
- replay(my);
- assertEquals(1, my.getInt(10));
- verify(my);
- }
-
- }
3.5 特殊的 Mock 对象类型
到目前为止,我们所创建的 Mock 对象都属于 EasyMock 默认的 Mock 对象类型,它对预期方法的调用次序不敏感,对非预期的方法调用抛出 AssertionError。除了这种默认的 Mock 类型以外,EasyMock 还提供了一些特殊的 Mock 类型用于支持不同的需求。
Strick Mock 对象
如果 Mock 对象是通过EasyMock.createMock() 或是 IMocksControl.createMock() 所创建的,那么在进行 verify 验证时,方法的调用顺序是不进行检查的。如果要创建方法调用的先后次序敏感的Mock 对象(Strick Mock),应该使用EasyMock.createStrickMock()来创建
类似于 createMock,我们同样可以用 IMocksControl 实例来创建一个 Strick Mock 对象:
IMocksControl control =EasyMock.createStrictControl();
Nice Mock 对象
使用 createMock() 创建的 Mock 对象对非预期的方法调用默认的行为是抛出 AssertionError,如果需要一个默认返回0,null 或 false 等"无效值"的"Nice Mock" 对象,可以通过 EasyMock 类提供的 createNiceMock() 方法创建。类似的,你也可以用IMocksControl 实例来创建一个 Nice Mock 对象。
举例:
- public interface MyTest {
- public boolean getBoolean();
- public int getInt(int i);
- public char getChar();
- public String getString();
- }
- public class MyTestOrder {
-
- @Test
- public void test() {
- MyTest my=createMock(MyTest.class);
-
-
-
-
-
- expect(my.getChar()).andReturn('b');
- expect(my.getInt(10)).andReturn(5);
- expect(my.getChar()).andReturn('c');
-
- replay(my);
-
- assertEquals(5, my.getInt(10));
- assertEquals('b', my.getChar());
- assertEquals('c', my.getChar());
- verify(my);
- }
-
- }
- public class MyTestOrderStrictMock {
-
- @Test
- public void test() {
- MyTest my=createStrictMock(MyTest.class);
-
-
-
-
-
- expect(my.getInt(10)).andReturn(5);
- expect(my.getChar()).andReturn('b');
-
- expect(my.getChar()).andReturn('c');
-
- replay(my);
-
-
-
-
-
-
-
-
-
- assertEquals(5, my.getInt(10));
- assertEquals('b', my.getChar());
- assertEquals('c', my.getChar());
- verify(my);
- }
-
- }
- public class MyTestOrderNiceMock {
- @Test
- public void test() {
- MyTest my=createNiceMock(MyTest.class);
-
-
-
-
-
-
-
-
-
- expect(my.getChar()).andReturn('b');
- expect(my.getString()).andReturn("world");
- expect(my.getInt(10)).andReturn(5);
-
- replay(my);
-
- assertEquals(5, my.getInt(10));
-
- assertEquals(true, my.getBoolean());
- assertEquals('b', my.getChar());
- assertEquals("world", my.getString());
-
- verify(my);
- }
- }
3.6 用EasyMock测试我们最开始的clock程序
- public abstract class Environmental {
- boolean playedWav = false;
-
- public abstract long getTime();
-
- public abstract void playWavFile(String fileName);
-
- public abstract boolean wavWasPlayed();
-
- public abstract void resetWav();
- }
- public class SystemEnvironment extends Environmental {
- public long getTime() {
- return System.currentTimeMillis();
- }
-
- public void playWavFile(String fileName) {
- playedWav = true;
- }
-
- public boolean wavWasPlayed() {
- return playedWav;
- }
-
- public void resetWav() {
- playedWav = false;
- }
- }
- public class Checker {
- private Environmental env;
-
- public Checker(Environmental env) {
- this.env = env;
- }
-
- public void reminder() {
- Calendar cal = Calendar.getInstance();
- cal.setTimeInMillis(env.getTime());
- int hour = cal.get(Calendar.HOUR_OF_DAY);
- if (hour >= 17) {
- env.playWavFile("quit_whistle.wav");
- }
- }
- }
- public class CheckerTest extends TestCase{
-
- public void testQuittingTime() {
-
-
-
-
- SystemEnvironment env = EasyMock.createMockBuilder(SystemEnvironment.class).addMockedMethod("getTime").createMock();
-
- Calendar cal = Calendar.getInstance();
- cal.set(Calendar.YEAR, 2006);
- cal.set(Calendar.MONTH, 11);
- cal.set(Calendar.DAY_OF_MONTH, 7);
- cal.set(Calendar.HOUR_OF_DAY, 16);
- cal.set(Calendar.MINUTE, 55);
-
-
- long t1 = cal.getTimeInMillis();
-
- long t2=t1+(5 * 60 * 1000);
-
- long t3=t2+ 2 * 60 * 60 * 1000;
-
-
-
-
- EasyMock.expect(env.getTime()).andReturn(t1).andReturn(t2).andReturn(t3);
-
- EasyMock.replay(env);
- Checker checker = new Checker(env);
- checker.reminder();
- assertFalse(env.wavWasPlayed());
-
-
- checker.reminder();
- assertTrue(env.wavWasPlayed());
- env.resetWav();
-
-
- t1 += 2 * 60 * 60 * 1000;
- checker.reminder();
- assertTrue(env.wavWasPlayed());
-
- EasyMock.verify(env);
- }
-
- }
四、EasyMock 的工作原理
EasyMock 是如何为一个特定的接口动态创建 Mock对象,并记录 Mock 对象预期行为的呢?其实,EasyMock 后台处理的主要原理是利用java.lang.reflect.Proxy 为指定的接口创建一个动态代理,这个动态代理,就是我们在编码中用到的 Mock 对象。EasyMock 还为这个动态代理提供了一个 InvocationHandler 接口的实现,这个实现类的主要功能就是将动态代理的预期行为记录在某个映射表中和在实际调用时从这个映射表中取出预期输出。
具体看链接部分: http://www.ibm.com/developerworks/cn/opensource/os-cn-easymock/
一、为什么要使用Mock工具
在做单元测试的时候,我们会发现我们要测试的方法会引用很多外部依赖的对象,比如:(发送邮件,网络通讯,远程服务, 文件系统等等)。 而我们没法控制这些外部依赖的对象,为了解决这个问题,我们就需要用到Mock工具来模拟这些外部依赖的对象,来完成单元测试。
二、为什么要使用PowerMock
现如今比较流行的Mock工具如jMock 、EasyMock 、Mockito等都有一个共同的缺点:不能mock静态、final、私有方法等。而PowerMock能够完美的弥补以上三个Mock工具的不足。
三、PowerMock简介
PowerMock是一个扩展了其它如EasyMock等mock框架的、功能更加强大的框架。PowerMock使用一个自定义类加载器和字节码操作来模拟静态方法,构造函数,final类和方法,私有方法,去除静态初始化器等等。通过使用自定义的类加载器,简化采用的IDE或持续集成服务器不需要做任何改变。熟悉PowerMock支持的mock框架的开发人员会发现PowerMock很容易使用,因为对于静态方法和构造器来说,整个的期望API是一样的。PowerMock旨在用少量的方法和注解扩展现有的API来实现额外的功能。目前PowerMock支持EasyMock和Mockito。
四、PowerMock入门
PowerMock有两个重要的注解:
–@RunWith(PowerMockRunner.class)
–@PrepareForTest( { YourClassWithEgStaticMethod.class })
如果你的测试用例里没有使用注解@PrepareForTest,那么可以不用加注解@RunWith(PowerMockRunner.class),反之亦然。当你需要使用PowerMock强大功能(Mock静态、final、私有方法等)的时候,就需要加注解@PrepareForTest。
五、PowerMock基本用法
(1) 普通Mock: Mock参数传递的对象
测试目标代码:
1 | public boolean callArgumentInstance(File file) { |
测试用例代码:
02 | public void testCallArgumentInstance() { |
04 | File file = PowerMockito.mock(File. class ); |
06 | ClassUnderTest underTest = new ClassUnderTest(); |
08 | PowerMockito.when(file.exists()).thenReturn( true ); |
10 | Assert.assertTrue(underTest.callArgumentInstance(file)); |
说明:普通Mock不需要加@RunWith和@PrepareForTest注解。
(2) Mock方法内部new出来的对象
测试目标代码:
01 | public class ClassUnderTest { |
03 | public boolean callInternalInstance(String path) { |
05 | File file = new File(path); |
测试用例代码:
01 | @RunWith (PowerMockRunner. class ) |
02 | public class TestClassUnderTest { |
05 | @PrepareForTest (ClassUnderTest. class ) |
06 | public void testCallInternalInstance() throws Exception { |
08 | File file = PowerMockito.mock(File. class ); |
10 | ClassUnderTest underTest = new ClassUnderTest(); |
12 | PowerMockito.whenNew(File. class ).withArguments( "bbb" ).thenReturn(file); |
14 | PowerMockito.when(file.exists()).thenReturn( true ); |
16 | Assert.assertTrue(underTest.callInternalInstance( "bbb" )); |
说明:当使用PowerMockito.whenNew方法时,必须加注解@PrepareForTest和@RunWith。注解@PrepareForTest里写的类是需要mock的new对象代码所在的类。
(3) Mock普通对象的final方法
测试目标代码:
1 | public class ClassUnderTest { |
3 | public boolean callFinalMethod(ClassDependency refer) { |
5 | return refer.isAlive(); |
01 | public class ClassDependency { |
03 | public final boolean isAlive() { |
测试用例代码:
01 | @RunWith (PowerMockRunner. class ) |
02 | public class TestClassUnderTest { |
05 | @PrepareForTest (ClassDependency. class ) |
06 | public void testCallFinalMethod() { |
08 | ClassDependency depencency = PowerMockito.mock(ClassDependency. class ); |
10 | ClassUnderTest underTest = new ClassUnderTest(); |
12 | PowerMockito.when(depencency.isAlive()).thenReturn( true ); |
14 | Assert.assertTrue(underTest.callFinalMethod(depencency)); |
说明: 当需要mock final方法的时候,必须加注解@PrepareForTest和@RunWith。注解@PrepareForTest里写的类是final方法所在的类。
(4) Mock普通类的静态方法
测试目标代码:
1 | public class ClassUnderTest { |
3 | public boolean callStaticMethod() { |
5 | return ClassDependency.isExist(); |
01 | public class ClassDependency { |
03 | public static boolean isExist() { |
测试用例代码:
01 | @RunWith (PowerMockRunner. class ) |
02 | public class TestClassUnderTest { |
05 | @PrepareForTest (ClassDependency. class ) |
06 | public void testCallStaticMethod() { |
08 | ClassUnderTest underTest = new ClassUnderTest(); |
10 | PowerMockito.mockStatic(ClassDependency. class ); |
12 | PowerMockito.when(ClassDependency.isExist()).thenReturn( true ); |
14 | Assert.assertTrue(underTest.callStaticMethod()); |
说明:当需要mock静态方法的时候,必须加注解@PrepareForTest和@RunWith。注解@PrepareForTest里写的类是静态方法所在的类。
(5) Mock 私有方法
测试目标代码:
01 | public class ClassUnderTest { |
03 | public boolean callPrivateMethod() { |
09 | private boolean isExist() { |
测试用例代码:
01 | @RunWith (PowerMockRunner. class ) |
02 | public class TestClassUnderTest { |
05 | @PrepareForTest (ClassUnderTest. class ) |
06 | public void testCallPrivateMethod() throws Exception { |
08 | ClassUnderTest underTest = PowerMockito.mock(ClassUnderTest. class ); |
10 | PowerMockito.when(underTest.callPrivateMethod()).thenCallRealMethod(); |
12 | PowerMockito.when(underTest, "isExist" ).thenReturn( true ); |
14 | Assert.assertTrue(underTest.callPrivateMethod()); |
说明:和Mock普通方法一样,只是需要加注解@PrepareForTest(ClassUnderTest.class),注解里写的类是私有方法所在的类。
(6) Mock系统类的静态和final方法
测试目标代码:
01 | public class ClassUnderTest { |
03 | public boolean callSystemFinalMethod(String str) { |
09 | public String callSystemStaticMethod(String str) { |
11 | return System.getProperty(str); |
测试用例代码:
01 | @RunWith (PowerMockRunner. class ) |
02 | public class TestClassUnderTest { |
05 | @PrepareForTest (ClassUnderTest. class ) |
06 | public void testCallSystemStaticMethod() { |
08 | ClassUnderTest underTest = new ClassUnderTest(); |
10 | PowerMockito.mockStatic(System. class ); |
12 | PowerMockito.when(System.getProperty( "aaa" )).thenReturn( "bbb" ); |
14 | Assert.assertEquals( "bbb" , underTest.callJDKStaticMethod( "aaa" )); |
说明:和Mock普通对象的静态方法、final方法一样,只不过注解@PrepareForTest里写的类不一样 ,注解里写的类是需要调用系统方法所在的类。
六 、无所不能的PowerMock
(1) 验证静态方法:
PowerMockito.verifyStatic();
Static.firstStaticMethod(param);
(2) 扩展验证:
PowerMockito.verifyStatic(Mockito.times(2)); // 被调用2次 Static.thirdStaticMethod(Mockito.anyInt()); // 以任何整数值被调用
(3) 更多的Mock方法
http://code.google.com/p/powermock/wiki/MockitoUsage13
七、PowerMock简单实现原理
• 当某个测试方法被注解@PrepareForTest标注以后,在运行测试用例时,会创建一个新的org.powermock.core.classloader.MockClassLoader实例,然后加载该测试用例使用到的类(系统类除外)。
• PowerMock会根据你的mock要求,去修改写在注解@PrepareForTest里的class文件(当前测试类会自动加入注解中),以满足特殊的mock需求。例如:去除final方法的final标识,在静态方法的最前面加入自己的虚拟实现等。
• 如果需要mock的是系统类的final方法和静态方法,PowerMock不会直接修改系统类的class文件,而是修改调用系统类的class文件,以满足mock需求。