EasyMock的简单使用(摘)


xp开发的一个利器--EasyMock。
EasyMock是一种 动态生成模仿对象以便应用于单元测试的工具,有了它可摆脱容器进行单元测试了。
它的使用很简单,下面一个简单登陆验证的例子:
public   class  LoginServlet  extends  HttpServlet {


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

public   class  LoginServletTest  extends  TestCase {

}

我们首先测试当用户名和口令验证失败的情形,演示如何使用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();

}

仔细观察测试代码,使用 EasyMock 来创建一个 Mock 对象需要首先创建一个 MockControl
MockControl mc  =  MockControl.createControl(HttpServletRequest. class );

然后,即可获得 MockControl 创建的 Mock 对象:
HttpServletRequest request  =  (HttpServletRequest)mc.getMock();

下一步,我们需要“录制” Mock 对象的预期行为。在 LoginServlet 中,先后调用了 request.getParameter("username") request.getParameter("password") 两个方法,因此,需要在 MockControl 中设置这两次调用后的指定返回值。我们期望返回的值为“ admin ”和“ 1234 ”:
request.getParameter( " username " );  //  期望下面的测试将调用此方法,参数为"username"
mc.setReturnValue( " admin " 1 );  //  期望返回值为"admin",仅调用1次

request.getParameter(
" password " );  //  期望下面的测试将调用此方法,参数为" password"

mc.setReturnValue(
" 1234 " 1 );  //  期望返回值为"1234",仅调用1次
紧接着,调用 mc.replay() ,表示 Mock 对象“录制”完毕,可以开始按照我们设定的方式运行,我们对 LoginServlet 进行测试,并预期会产生一个 RuntimeException
LoginServlet servlet  =   new  LoginServlet();

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对象外,还需要模拟ServletContextRequestDispatcher对象:


MockControl requestCtrl  =  MockControl.createControl(HttpServletRequest. class );

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();

然后,测试 doPost() 方法,这里,为了让 getServletContext() 方法返回我们创建的 ServletContext Mock 对象,我们定义一个匿名类并覆写 getServletContext() 方法:
LoginServlet servlet  =   new  LoginServlet() {

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摘的:

// 设定BookManager MockObject
        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 " );


你可能感兴趣的:(编程,servlet,单元测试,JUnit,XP)