在单元测试的策略中伪对象被广泛使用他从测试中分离了外部的不需要的因素并且帮助开发人员专注于被测试的功能
EasyMock是一个在这方面很有名的工具可以在运行时为给定的接口创建伪对象伪对象的行为可以在测试用例中的执行测试代码之前被定义EasyMock基于javalangreflectProxy他可以根据给定的接口创建动态代理类或者对象但因为使用Proxy使得他有一个天生的缺陷只能创建基于接口的伪对象
Mocquer是一个类似的工具但他扩展了EasyMock的功能能够支持创建类的伪对象
Mocquer介绍
Mocquer基于Dunamis项目被用来为特定的类或接口生成动态代理类或对象为方便使用他遵循EasyMock的类和方法的命名规范只是在内部使用不同的实现方法
MockControl是Mocquer项目中最重要的类他被用来控制伪对象的生命周期和行为定义这个类中有四类方法
生命周期控制方法
·public void replay();
·public void verify();
·public void reset();
伪对象在他的生命周期中有三种状态准备态工作态验证态图显示了伪对象的生命周期
Figure Mock object life cycle
刚开始伪对象处于准备态他的表现行为可以在这里定义replay()将改变伪对象的状态为工作态在这个状态中所有伪对象的方法调用将会遵循在准备态下定义的行为在verify()调用后伪对象就处于验证态MockControl会比较伪对象的预定义行为与实际行为是否匹配匹配规则依赖于使用的MockControl类型这个会在稍后解释开发人员可以在需要时调用replay()来重现预定义的行为而任何状态下调用reset()将会清除状态历史并重置为初始的准备态
工厂方法
·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 clazzClass[] argTypes Object[] args);
如果类是作为接口来模拟的或者他有一个公共或保护的缺省构造函数则第一个版本的方法会被使用否则第二个版本会被用来定义标识和提供参数给期望的构造函数
例如假设ClassWithNoDefaultConstructor是一个没有缺省构造函数的类
public class ClassWithNoDefaultConstructor {
public ClassWithNoDefaultConstructor(int i) {
}
}
·伪对象获取方法
public Object getMock();
每一个MockControl包含一个生成的伪对象的引用开发人员可以使用这个方法取得伪对象并且转换为实际的对象类型
//get mock control
MockControl control = MockControlcreateControl(Fooclass);
//Get the mock object from mock control
Foo foo = (Foo) controlgetMock();
·行为定义方法
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类
//Foojava
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 = MockControlcreateControl(Fooclass);
//get mock object
Foo foo = (Foo)controlgetMock();
//begin behavior definition
//specify which method invocations behavior
//to be defined
foobar();
//define the behavior return ok when the
//argument is
controlsetReturnValue(ok);
//end behavior definition
controlreplay();
MockControl中超过个方法是行为定义方法他们可以如下分类
osetReturnValue()
这些方法被用来定义最后的方法调用应该返回一个值作为参数这儿有个使用原始类型作业参数的`setReturnValue()方法如setReturnValue(int i)或setReturnValue(float f)setReturnValue(Object obj)被用来满足那些需要对象作为参数的方法如果给定的值不匹配方法的返回值则抛出AssertionFailedError异常
当然也可以在行为中加入预期调用的次数这称为调用次数限制
MockControl control =
Foo foo = (Foo)controlgetMock();
foobar();
//define the behavior return ok when the
//argument is And this method is expected
//to be called just once
setReturnValue(ok );
上面的代码段定义了bar()方法只能被调用一次如果提供一个范围又会怎么样呢?
foobar();
//define the behavior return ok when the
//argument is And this method is expected
//to be called at least once and at most
//times
setReturnValue(ok );
现在bar()被限制至少被调用一次最多次更方便的是Range已经预定义了一些限制范围
foobar();
//define the behavior return ok when the
//argument is And this method is expected
//to be called at least once
setReturnValue(ok RangeONE_OR_MORE);
RangeONE_OR_MORE是一个预定义的Range实例这意味着方法应该被调用至少一次如果setReturnValue()中没有定义调用次数限制如setReturnValue(Hello)RangeONE_OR_MORE被认为是缺省值还有两个预定义的Range实例RangeONE(就一次)和RangeZERO_OR_MORE(对调用次数没有限制)
这儿还有一个特定的设置返回值的方法setDefaultReturnValue()他将代替方法的参数值作为返回值缺省的调用次数限制为RangeONE_OR_MORE这被称为方法参数值敏感性
foobar();
//define the behavior return ok when calling
//bar(int) despite the argument value
setDefaultReturnValue(ok);
osetThrowable
setThrowable(Throwable throwable)被用来定义方法调用异常抛出的行为如果给定的throwable不匹配方法的异常定义则AssertionFailedError会被抛出调用次数的限制与方法参数值敏感性是一致的
try {
foodummy();
} catch (Exception e) {
//skip
}
//define the behavior throw ParseException
//when call dummy() And this method is expected
//to be called exactly once
controlsetThrowable(new ParseException( ) );
osetVoidCallable()
setVoidCallable()被用于没有返回值的方法调用次数的限制与方法参数值敏感性是一致的
try {
foodummy();
} catch (Exception e) {
//skip
}
//define the behavior no return value
//when calling dummy() And this method is expected
//to be called at least once
controlsetVoidCallable();
o