3.4 JMock测试框架
JMock是用于创建Mock对象的工具框架,它基于Java开发,在Java测试与开发环境中有不可比拟的优势。更重要的是,JMock大大简化了虚拟对象的使用。
3.4.1 驱动和桩
3.3节讲到了驱动和桩,那么究竟什么是驱动呢?这个很好理解,测试夹具就是驱动。那么什么是桩呢?桩有何价值呢?
举一个简单的例子,一个简单的程序有3个函数,即A、B、C,函数的调用关系为A→B→C,那么现在要测试函数C,只需要写一个驱动去调用C即可。那么如果要测试B呢?驱动肯定要有。另外,为了使B测试时不受C的影响,需要隔离C,那么怎么隔离呢?用桩C#代替C,让B调用C#而不是C。而C#桩是可以由测试人员来控制的,想让它返回什么结果就返回什么结果,这样就可以达到隔离的效果。
再考虑另外一种场景,现在要对B函数进行测试,但是C函数还没有实现。毫无疑问,若B函数是无法运行的,测试必然无法正常进行。那么怎么办?写一个桩——C##,伪装成C好像已经实现,这样B就可以成功运行,从而达到测试目的。
例如,StringHandle类中的isNumber方法还没有实现,那么可以采用这种方法来实现isNumber,代码如图3-36所示。
public boolean isNumber(String source) {
return true;
}
图3-36 关于isNumber的示例
以上代码完全可以让调用isNumber的代码运行起来,不过这里有一个比较严重的问题:将开发的代码更改了,强行在StringHandle类中加入了一个“伪装”的isNumber方法。这显然会有问题,测试人员不能通过修改开发的原始代码来达到测试目的。另外,通常被测代码不一定是源代码,有可能是打包好的JAR包,调用JAR包中的接口来进行测试,这个时候是没有办法改变代码的,那么究竟如何处理,才能从根本上解决这一问题呢?答案是使用Mock对象。
3.4.2 Mock对象
顾名思义,Mock对象用来假冒一个方法,达到隔离环境的目的,适用于对一些还没有实现的方法的模拟。其实,只要真正理解了“多态”的使用,使用Mock对象就不会有任何问题,所以Mock的核心在于“多态”。下面基于多态特性来介绍Mock对象。
要使用Mock,必须借助多态性。同样,要使用多态性,必须借助接口。显然,之前并没有使用任何接口来定义规范,而直接使用类方法来完成。所以,需要对StringHandle类进行适当改造,让它可以实现多态性。为了对比改造后与改造前的区别,建议新建一个包,专门存放改造后的代码,在此创建一个新的包com.learn.updated,将com.learn.compare目录下的所有源代码复制过来。
首先,定义IString接口,定义方法isNumber,代码如图3-37所示。
package com.learn.updated;
public interface IString {
public boolean isNumber(String source);
}
图3-37 定义接口和方法
然后,把StringHandle类改造成实现IString接口的实现类,代码如图3-38所示。
package com.learn.updated;
import Java.io.BufferedReader;
import Java.io.IOException;
import Java.io.InputStreamReader;
图3-38 改造类
import Java.util.Arrays;
import Java.util.Vector;
public class StringHandle {
public IString istring; // 定义接口变量istring
// 从控制台输入字符串
public Integer[] inputString() {
// 代码不变,此处省略
}
// 将字符串解析成数组
public Integer[] splitString(String source, String delimiter) {
Vector vector = new Vector();
int position = source.indexOf(delimiter);
while (source.contains(delimiter)) {
String value = source.substring(0, position);
if (istring.isNumber(value)) { // 将该行修改成istring.isNumber
vector.add(Integer.parseInt(value));
}
else {
Integer[] result = {1};
return result;
}
source = source.substring(position + 1, source.length());
position = source.indexOf(delimiter);
}
vector.add(Integer.parseInt(source));
Integer[] array = new Integer[vector.size()];
vector.copyInto(array);
return array;
}
// 检查字符串是否可正常转换为数字
public boolean isNumber(String source) {
// 删除该方法
}
}
图3-38 改造类(续)
以上代码的核心在于使用istring来调用其方法isNumber,而不是直接通过this.isNumber调用,这样就可以通过将不同的实例传递给istring来实现Mock的方法。
接下来,创建一个新的包com.learn.jmock,并创建一个Mock类MockIString.Java,代码如图3-39所示。
package com.learn.jmock;
import com.learn.updated.*;
public class MockIString implements IString {
图3-39 创建包和类
public boolean isNumber(String source) {
return true;
}
}
图3-39 创建包和类(续)
最后,使用Mock对象来对StringHandle.splitString进行测试,具体代码(StringHandle- Mock.Java)如图3-40所示。
package com.learn.jmock;
import static org.junit.Assert.assertArrayEquals;
import org.junit.Test;
import com.learn.updated.StringHandle;
public class StringHandleMock {
@Test
public void splitString() {
StringHandle stringHandle = new StringHandle();
stringHandle.istring = new MockIString();
String source = "333,111,222,666";
Integer[] expect = {333, 111, 222, 666};
Integer[] actual = stringHandle.splitString(source, ",");
assertArrayEquals(expect, actual);
}
}
图3-40 进行测试的具体代码
上述代码通过了测试,虽然看到StringHandle中并没有实现isNumber,但是由于在MockIString中实现了它,利用多态性将MockIString的实例传递给StringHandle类的istring接口变量,这样splitString方法就可以正常使用istring.isNumber了。这样做并不会修改到被测试的源代码,从根本上解决了使用桩所遇到的各种问题。
Mock对象的使用范畴如下。
●真实对象具有不可确定的行为,产生不可预测的效果(如股票行情、天气预报)。
●真实对象很难创建。
●真实对象的某些行为很难触发。
●真实对象实际上还不存在(和其他开发小组或者新的硬件交互)等。
使用Mock对象测试的关键步骤如下。
(1)使用一个接口来描述这个对象。
(2)在产品代码中实现这个接口。
(3)在测试代码中实现这个接口。
在被测代码中只通过接口来引用对象,所以代码不知道引用的对象是真实对象还是Mock对象。
版权声明:51Testing软件测试网获得人民邮电出版社和作者授权连载本书部分章节。
任何个人或单位未获得明确的书面许可,不得对本文内容复制、转载或进行镜像,否则将追究法律责任。