EasyMock的简单使用(摘)
EasyMock是一种 动态生成模仿对象以便应用于单元测试的工具,有了它可摆脱容器进行单元测试了。
它的使用很简单,下面一个简单登陆验证的例子:
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String username = request.getParameter( " username " );
String password = request.getParameter( " password " );
// check username & password:
if ( " admin " .equals(username) && " 123456 " .equals(password)) {
ServletContext context = getServletContext();
RequestDispatcher dispatcher = context.getNamedDispatcher( " dispatcher " );
dispatcher.forward(request, response);
}
else {
throw new RuntimeException( " Login failed. " );
}
}
}
这个 Servlet 实现简单的用户验证的功能,若用户名和口令匹配“ admin ”和“ 123456 ”,则请求被转发到指定的 dispatcher 上,否则,直接抛出 RuntimeException 。
为了测试 doPost() 方法,我们需要模拟 HttpServletRequest , ServletContext 和 RequestDispatcher 对象,以便脱离 J2EE 容器来测试这个 Servlet 。
我们建立 TestCase ,名为 LoginServletTest :
}
我们首先测试当用户名和口令验证失败的情形,演示如何使用EasyMock来模拟HttpServletRequest对象:
public void testLoginFailed() throws Exception {
MockControl mc = MockControl.createControl(HttpServletRequest. class );
HttpServletRequest request = (HttpServletRequest)mc.getMock();
// set Mock Object behavior:
request.getParameter( " username " );
mc.setReturnValue( " admin " , 1 );
request.getParameter( " password " );
mc.setReturnValue( " 1234 " , 1 );
// ok, all behaviors are set!
mc.replay();
// now start test:
LoginServlet servlet = new LoginServlet();
try {
servlet.doPost(request, null );
fail( " Not caught exception! " );
}
catch (RuntimeException re) {
assertEquals( " Login failed. " , re.getMessage());
}
// verify:
mc.verify();
}
然后,即可获得 MockControl 创建的 Mock 对象:
下一步,我们需要“录制” Mock 对象的预期行为。在 LoginServlet 中,先后调用了 request.getParameter("username") 和 request.getParameter("password") 两个方法,因此,需要在 MockControl 中设置这两次调用后的指定返回值。我们期望返回的值为“ admin ”和“ 1234 ”:
request.getParameter( " password " ); // 期望下面的测试将调用此方法,参数为" password"
mc.setReturnValue( " 1234 " , 1 ); // 期望返回值为"1234",仅调用1次
try {
servlet.doPost(request, null );
fail( " Not caught exception! " );
}
catch (RuntimeException re) {
assertEquals( " Login failed. " , re.getMessage());
}
由于本次测试的目的是检查当用户名和口令验证失败后, LoginServlet 是否会抛出 RuntimeException ,因此, response 对象对测试没有影响,我们不需要模拟它,仅仅传入 null 即可。
最后,调用 mc.verify() 检查 Mock 对象是否按照预期的方法调用正常运行了。
运行 JUnit ,测试通过!表示我们的 Mock 对象正确工作了!
下一步,我们来测试当用户名和口令匹配时,LoginServlet应当把请求转发给指定的RequestDispatcher。在这个测试用例中,我们除了需要HttpServletRequest Mock对象外,还需要模拟ServletContext和RequestDispatcher对象:
HttpServletRequest requestObj = (HttpServletRequest)requestCtrl.getMock();
MockControl contextCtrl = MockControl.createControl(ServletContext. class );
final ServletContext contextObj = (ServletContext)contextCtrl.getMock();
MockControl dispatcherCtrl = MockControl.createControl(RequestDispatcher. class );
RequestDispatcher dispatcherObj = (RequestDispatcher)dispatcherCtrl.getMock();
按照doPost()的语句顺序,我们设定Mock对象指定的行为:
requestObj.getParameter( " username " );
requestCtrl.setReturnValue( " admin " , 1 );
requestObj.getParameter( " password " );
requestCtrl.setReturnValue( " 123456 " , 1 );
contextObj.getNamedDispatcher( " dispatcher " );
contextCtrl.setReturnValue(dispatcherObj, 1 );
dispatcherObj.forward(requestObj, null );
dispatcherCtrl.setVoidCallable( 1 );
requestCtrl.replay();
contextCtrl.replay();
dispatcherCtrl.replay();
public ServletContext getServletContext() {
return contextObj;
}
};
servlet.doPost(requestObj, null );
最后,检查所有Mock对象的状态:
requestCtrl.verify();
contextCtrl.verify();
dispatcherCtrl.verify();
运行 JUnit ,测试通过!
倘若 LoginServlet 的代码有误,例如,将 context.getNamedDispatcher("dispatcher") 误写为 context.getNamedDispatcher("dispatcher2") ,则测试失败, JUnit 报告:
junit.framework.AssertionFailedError:
Unexpected method call getNamedDispatcher("dispatcher2"):
getNamedDispatcher("dispatcher2"): expected: 0, actual: 1
getNamedDispatcher("dispatcher"): expected: 1, actual: 0
at ...
总结:
虽然 EasyMock 可以用来模仿依赖对象,但是,它只能动态模仿接口,无法模仿具体类。这一限制正好要求我们遵循“针对接口编程”的原则:如果不针对接口,则测试难于进行。应当把单元测试看作是运行时代码的最好运用,如果代码在单元测试中难于应用,则它在真实环境中也将难于应用。总之,创建尽可能容易测试的代码就是创建高质量的代码。
现在,easymock可以模仿类了,以下是在springside摘的:
bookManagerMockControl = MockClassControl.createControl(BookManager. class );
bookManagerMock = (BookManager) bookManagerMockControl.getMock();
controller.setBookManager(bookManagerMock);
// 录制getAllBook()和getCategorys方法的期望值
bookManagerMock.getAllBook();
bookManagerMockControl.setReturnValue( new ArrayList());
bookManagerMockControl.replay();
// 执行操作
mv = controller.handleRequest(request, response);
// 验证结果
assertModelAttributeAvailable(mv, " books " );