NXP

EasyMock的简单使用(摘)

2019-07-12 12:44发布

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