在单元测试的策略中伪对象被广泛使用。他从测试中分离了外部的不需要的因素并且帮助开发人员专注于被测试的功能。
EasyMock是一个在这方面很有名的工具,可以在运行时为给定的接口创建伪对象。伪对象的行为可以在测试用例中的执行测试代码之前被定义。EasyMock基于java.lang.reflect.Proxy,他可以根据给定的接口创建动态代理类或者对象。但因为使用Proxy使得他有一个天生的缺陷:只能创建基于接口的伪对象。
Mocquer是一个类似的工具,但他扩展了EasyMock的功能能够支持创建类的伪对象。
Mocquer介绍 Mocquer基于Dunamis项目,被用来为特定的类或接口生成动态代理类或对象。为方便使用,他遵循EasyMock的类和方法的命名规范,只是在内部使用不同的实现方法。
MockControl是Mocquer项目中最重要的类。他被用来控制伪对象的生命周期和行为定义。这个类中有四类方法。
1、生命周期控制方法:
·public void replay();
·public void verify();
·public void reset();
伪对象在他的生命周期中有三种状态:准备态、工作态、验证态。图1显示了伪对象的生命周期。
Figure 1. Mock object life cycle
刚开始,伪对象处于准备态,他的表现行为可以在这里定义。replay()将改变伪对象的状态为工作态。在这个状态中所有伪对象的方法调用将会遵循在准备态下定义的行为。在verify()调用后,伪对象就处于验证态。MockControl会比较伪对象的预定义行为与实际行为是否匹配。匹配规则依赖于使用的MockControl类型,这个会在稍后解释。开发人员可以在需要时调用replay()来重现预定义的行为。而任何状态下调用reset()将会清除状态历史并重置为初始的准备态。
2、工厂方法
·public static MockControl createNiceControl(...);
·public static MockControl createControl(...);
·public static MockControl createStrictControl(...);
Mocquer提供了三种MockControl:宽松的,普通的和严格的。开发人员可以在自己的测试用例中根据测试的内容(测试点)和测试的执行方式(测试策略)选择相应的MockControl。宽松的MockControl是最随意的,他不关心伪对象中方法调用的顺序,甚至未预期的方法调用,只是返回一个缺省值(依赖于方法的返回值)。普通的MockControl比宽松的MockControl严格些,未预期的方法调用会导致AssertionFailedError异常。严格的MockControl是最严格的,如果伪对象在工作态下方法调用的顺序与准备态的不同,就会抛出AssertionFailedError异常。下表显示了三种不同MockControl的区别。
下面是每一个工厂方法的两个不同版本。
public static MockControl createXXXControl(Class clazz); public static MockControl createXXXControl(Class clazz, Class[] argTypes, Object[] args);
如果类是作为接口来模拟的或者他有一个公共或保护的缺省构造函数,则第一个版本的方法会被使用。否则第二个版本会被用来定义标识和提供参数给期望的构造函数。
例如,假设ClassWithNoDefaultConstructor是一个没有缺省构造函数的类:
public class ClassWithNoDefaultConstructor {
public ClassWithNoDefaultConstructor(int i) {
...
}
...
}
·伪对象获取方法
public Object getMock();
每一个MockControl包含一个生成的伪对象的引用。开发人员可以使用这个方法取得伪对象并且转换为实际的对象类型。
//get mock control
MockControl control = MockControl.createControl(Foo.class);
//Get the mock object from mock control
Foo foo = (Foo) control.getMock();
·行为定义方法
public void setReturnValue(... value);
public void setThrowable(Throwable throwable);
public void setVoidCallable();
public void setDefaultReturnValue(... value);
public void setDefaultThrowable(Throwable throwable);
public void setDefaultVoidCallable();
public void setMatcher(ArgumentsMatcher matcher);
public void setDefaultMatcher(ArgumentsMatcher matcher);
MockControl允许开发人员定义伪对象的每一个方法的行为。当他在准备态时,开发人员可以调用伪对象的方法。首先规定哪一个调用方法的行为需要被定义。然后开发人员可以使用行为定义的方法之一来定义行为。例如,看一下下面的Foo类:
//Foo.java
public class Foo {
public void dummy() throw ParseException {
...
} public String bar(int i) {
...
} public boolean isSame(String[] strs) {
...
} public void add(StringBuffer sb, String s) {
...
}
}
伪对象的行为可以按照下面的方式来定义:
//get mock control
MockControl control = MockControl.createControl(Foo.class);
//get mock object
Foo foo = (Foo)control.getMock();
//begin behavior definition
//specify which method invocation's behavior
//to be defined.
foo.bar(10);
//define the behavior -- return "ok" when the
//argument is 10
control.setReturnValue("ok");
...
//end behavior definition
control.replay();
...
MockControl中超过50个方法是行为定义方法。他们可以如下分类。
o setReturnValue()
这些方法被用来定义最后的方法调用应该返回一个值作为参数。这儿有7个使用原始类型作业参数的`setReturnValue()方法,如setReturnValue(int i)或setReturnValue(float f)。setReturnValue(Object obj)被用来满足那些需要对象作为参数的方法。如果给定的值不匹配方法的返回值,则抛出AssertionFailedError异常。
当然也可以在行为中加入预期调用的次数。这称为调用次数限制。
MockControl control =
...
Foo foo = (Foo)control.getMock();
...
foo.bar(10);
//define the behavior -- return "ok" when the
//argument is 10. And this method is expected
//to be called just once.
setReturnValue("ok", 1);
...
上面的代码段定义了bar(10)方法只能被调用一次。如果提供一个范围又会怎么样呢?
...
foo.bar(10);
//define the behavior -- return "ok" when the
//argument is 10. And this method is expected
//to be called at least once and at most 3
//times.
setReturnValue("ok", 1, 3);
...
现在bar(10)被限制至少被调用一次最多3次。更方便的是Range已经预定义了一些限制范围。
...
foo.bar(10);
//define the behavior -- return "ok" when the
//argument is 10. And this method is expected
//to be called at least once.
setReturnValue("ok", Range.ONE_OR_MORE);
...
Range.ONE_OR_MORE是一个预定义的Range实例,这意味着方法应该被调用至少一次。如果setReturnValue()中没有定义调用次数限制,如setReturnValue("Hello"),Range.ONE_OR_MORE被认为是缺省值。还有两个预定义的Range实例,Range.ONE(就一次)和Range.ZERO_OR_MORE(对调用次数没有限制)。
这儿还有一个特定的设置返回值的方法:setDefaultReturnValue()。他将代替方法的参数值作为返回值,缺省的调用次数限制为Range.ONE_OR_MORE。这被称为方法参数值敏感性。
...
foo.bar(10);
//define the behavior -- return "ok" when calling
//bar(int) despite the argument value.
setDefaultReturnValue("ok");
...
o setThrowable
setThrowable(Throwable throwable)被用来定义方法调用异常抛出的行为。如果给定的throwable不匹配方法的异常定义,则AssertionFailedError会被抛出。调用次数的限制与方法参数值敏感性是一致的。
...
try {
foo.dummy();
} catch (Exception e) {
//skip
}
//define the behavior -- throw ParseException
//when call dummy(). And this method is expected
//to be called exactly once.
control.setThrowable(new ParseException("", 0), 1);
...
o setVoidCallable()
setVoidCallable()被用于没有返回值的方法。调用次数的限制与方法参数值敏感性是一致的。
...
try {
foo.dummy();
} catch (Exception e) {
//skip
}
//define the behavior -- no return value
//when calling dummy(). And this method is expected
//to be called at least once.
control.setVoidCallable();
...
o