在实际编写unit test的过程中,很多情况下我们会需要使用其他的组件(如和数据库相关的测试)。当我们是使用第三方提供的组件时,这并不是太大的问题,因为我们可以假设他们是无错的。但是一旦这些组件是我们自己的组件时,问题就暴露出来了。
“单元测试出错了,但到底是谁的错。我的?他的?”,这种情况显然违反了测试的独立性原则。出现这种情况使得这个测试无法确切的指出是那个单元出现了问题,照成了排错的困难,而且也浪费了时间。同时,过分的将单元的测试代码依赖于其他单元,也照成了其他一些很现实的问题:
- 在所依赖的单元完成之前,无法顺利的编写单元测试。
- 产生环境的依赖性,如运行一个HttpServletRequest处理器的测试代码必须启动一个servlet容器。
所有这些问题,都可以使用Mock Object来解决。使用它的前提是,所依赖单元的接口必须定义清楚。而EasyMock正是为了这一目的而产生的。
使用目的
通过模拟unit test所需要的组件,已达到隔离各个unit test的目的。目前的版本1.1,它所需要的环境是jdk1.3.1以上和junit3.8.1以上。
可以从http://sourceforge.net/projects/easymock/处下载。
用法
1. EasyMock采用“记录-----回放”的工作模式,基本使用步骤:
- 创建Mock对象的控制对象Control。
- 从控制对象中获取所需要的Mock对象。
- 记录测试方法中所使用到的方法和返回值。
- 设置Control对象到“回放”模式。
- 进行测试。
- 在测试完毕后,确认Mock对象已经执行了刚才定义的所有操作。
2. 使用举例:假设需要测试的对象是RequestUtil,所需要测试的方法为getBoolean。此时我们需要模拟一个HttpServletRequest对象。
public void testGetStringHttpServletRequestString() {
public void testGetBoolean() {
//创建Mock对象的控制器
MockControl control= MockControl.createControl( HttpServletRequest.class);
//获取Mock对象
HttpServletRequest mock= (HttpServletRequest)control.getMock();
//设置getBoolean中要使用的方法和返回值
control.expectAndReturn( mock.getParameter( "test"), null);
//设置控制器为replay模式
control.replay();
//执行测试
assertNull( RequestUtil.getString( mock, "test"));
//确认
control.verify();
}
}
通过EasyMock,执行这段测试代码完全不需要启动一个servlet容器,在命令行的方式下即可完成,非常的方便。
3. 记录需要使用的Mock的行为。在使用一个Mock对象之前,需要设置我们要用到的方法,以及每个方法的返回值。对于那些没有设置的方法,一旦调用(控制器处于replay模式),EasyMock就会抛出异常。记录一个方法,通常可以分成2步:首先,如同使用正常对象调用这个方法;然后,使用控制器的setReturnValue函数设置即可。在1.1中,提供了expectAndReturn函数,使得2步可以合而为一。主要的函数大致如下:
- expectAndReturn,设置期望调用的函数,以及返回值
- expectAndThrow,设置期望调用的函数,同时期望该次调用抛出异常
- setReturnValue,设置上一次调用的返回值(如上次调用时,request.getparameter( “test”),此处设置request.getparameter( “test”)的返回值)
- setThrowable,设置上次调用抛出的异常
在EasyMock中还可以设置调用所执行的次数,具体细节请参见对应的javaDoc。
4. 使用举例:
MockControl control= MockControl.createControl( HttpServletRequest.class);
HttpServletRequest mock= (HttpServletRequest)control.getMock();
mock.getParameter( "test");
//设置第一次调用request.getParameter的返回值
control.setReturnValue( null, 1);
//设置第二次调用request.getParameter的返回值
control.setReturnValue("this is a test", 1);
control.replay();
assertEquals( RequestUtil.getString( mock, "test", "haha"), "haha");
assertEquals( RequestUtil.getString( mock, "test"), "this is a test");
control.verify();
5. 在Mock对象使用结束后,务必调用控制器的verify函数,以确认Mock对象的方法得到了调用。
6. 调用方法的次序,使用Strict。有时,测试代码依赖于被依赖组件的方法的次序。如在测试与数据库相关代码时,测试代码很有可能是这样的次序:先打开数据库链接,执行操作,关闭链接。为了更好的测试这样的代码,可以使用MockControl.createStrictControl()来创建一个严格的Mock对象控制器,在其中,他会规定Mock出来的对象的调用次序。
以上是EasyMock的主要使用,至于其他的用法,请参见具体的文档。
检查表
在此列出使用Mock对象来进行unit test需要注意的问题:
- 当自己动手实现Mock对象时,不要在Mock对象中实现业务逻辑。