`

一个Easy Mock案例(servelet)

 
阅读更多

一个Easy Mock案例(servelet)

单元测试是XP极力推荐的测试驱动 开发模式,是保证软件 质量的重要方法。尽管如此,对许多类的单元测试仍然是极其困难的,例如,对数据库操作的类进行测试,如果不准备好数据库环境以及相关测试数据,是很难进行单元测试的;再例如,对需要运行在容器内的Servlet或EJB组件,脱离了容器也难于测试。



幸运的是,Mock Object可以用来模拟一些我们需要的类,这些对象被称之为模仿对象,在单元测试中它们特别有价值。



Mock Object用于模仿真实对象的方法调用,从而使得测试不需要真正的依赖对象。Mock Object只为某个特定的测试用例的场景提供刚好满足需要的最少功能。它们还可以模拟错误的条件,例如抛出指定的异常等。



目前,有许多可用的Mock类库可供我们选择。一些Mock库提供了常见的模仿对象,例如:HttpServletRequest,而另一些Mock库则提供了动态生成模仿对象的功能,本文将讨论使用EasyMock动态生成模仿对象以便应用于单元测试。



到目前为止,EasyMock提供了1.2版本和2.0版本,2.0版本仅支持Java SE 5.0,本例中,我们选择EasyMock 1.2 for Java 1.3版本进行测试,可以从http://www.easymock.org/ 下载合适的版本。



我们首先来看一个用户验证的LoginServlet类:

Java代码 复制代码收藏代码
  1. packagecom.jeffhanson.geronimo.jmx;
  2. importjava.io.*;
  3. importjavax.servlet.*;
  4. importjavax.servlet.http.*;
  5. publicclassLoginServletextendsHttpServlet{
  6. protectedvoiddoPost(HttpServletRequestrequest,
  7. HttpServletResponseresponse)throwsServletException,IOException{
  8. Stringusername=request.getParameter("username");
  9. Stringpassword=request.getParameter("password");
  10. //checkusername&password:
  11. if("admin".equals(username)&&"123456".equals(password)){
  12. ServletContextcontext=getServletContext();
  13. RequestDispatcherdispatcher=context
  14. .getNamedDispatcher("dispatcher");
  15. dispatcher.forward(request,response);
  16. }
  17. else{
  18. thrownewRuntimeException("Loginfailed.");
  19. }
  20. }
  21. }
  22. packagecom.jeffhanson.geronimo.jmx;
  23. importjava.io.*;
  24. importjavax.servlet.*;
  25. importjavax.servlet.http.*;
  26. publicclassLoginServletextendsHttpServlet{
  27. protectedvoiddoPost(HttpServletRequestrequest,
  28. HttpServletResponseresponse)throwsServletException,IOException{
  29. Stringusername=request.getParameter("username");
  30. Stringpassword=request.getParameter("password");
  31. //checkusername&password:
  32. if("admin".equals(username)&&"123456".equals(password)){
  33. ServletContextcontext=getServletContext();
  34. RequestDispatcherdispatcher=context
  35. .getNamedDispatcher("dispatcher");
  36. dispatcher.forward(request,response);
  37. }
  38. else{
  39. thrownewRuntimeException("Loginfailed.");
  40. }
  41. }
  42. }
这个Servlet实现简单的用户验证的功能,若用户名和口令匹配“admin”和“123456”,则请求被转发到指定的dispatcher上,否则,直接抛出RuntimeException。



为了测试doPost()方法,我们需要模拟HttpServletRequest,ServletContext和RequestDispatcher对象,以便脱离J2EE容器来测试这个Servlet。



我们建立TestCase,名为LoginServletTest:

Java代码 复制代码收藏代码
  1. publicclassLoginServletTestextendsTestCase{
  2. }
  3. publicclassLoginServletTestextendsTestCase{
  4. }
我们首先测试当用户名和口令验证失败的情形,演示如何使用EasyMock来模拟HttpServletRequest对象:


Java代码 复制代码收藏代码
  1. publicvoidtestLoginFailed()throwsException{
  2. MockControlmc=MockControl.createControl(HttpServletRequest.class);
  3. HttpServletRequestrequest=(HttpServletRequest)mc.getMock();
  4. //setMockObjectbehavior:
  5. request.getParameter("username");
  6. mc.setReturnValue("admin",1);
  7. request.getParameter("password");
  8. mc.setReturnValue("1234",1);
  9. //ok,allbehaviorsareset!
  10. mc.replay();
  11. //nowstarttest:
  12. LoginServletservlet=newLoginServlet();
  13. try{
  14. servlet.doPost(request,null);
  15. fail("Notcaughtexception!");
  16. }
  17. catch(RuntimeExceptionre){
  18. assertEquals("Loginfailed.",re.getMessage());
  19. }
  20. //verify:
  21. mc.verify();
  22. }
  23. publicvoidtestLoginFailed()throwsException{
  24. MockControlmc=MockControl.createControl(HttpServletRequest.class);
  25. HttpServletRequestrequest=(HttpServletRequest)mc.getMock();
  26. //setMockObjectbehavior:
  27. request.getParameter("username");
  28. mc.setReturnValue("admin",1);
  29. request.getParameter("password");
  30. mc.setReturnValue("1234",1);
  31. //ok,allbehaviorsareset!
  32. mc.replay();
  33. //nowstarttest:
  34. LoginServletservlet=newLoginServlet();
  35. try{
  36. servlet.doPost(request,null);
  37. fail("Notcaughtexception!");
  38. }
  39. catch(RuntimeExceptionre){
  40. assertEquals("Loginfailed.",re.getMessage());
  41. }
  42. //verify:
  43. mc.verify();
  44. }
仔细观察测试代码,使用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:

Java代码 复制代码收藏代码
  1. LoginServletservlet=newLoginServlet();
  2. try{
  3. servlet.doPost(request,null);
  4. fail("Notcaughtexception!");
  5. }
  6. catch(RuntimeExceptionre){
  7. assertEquals("Loginfailed.",re.getMessage());
  8. }
  9. LoginServletservlet=newLoginServlet();
  10. try{
  11. servlet.doPost(request,null);
  12. fail("Notcaughtexception!");
  13. }
  14. catch(RuntimeExceptionre){
  15. assertEquals("Loginfailed.",re.getMessage());
  16. }
由于本次测试的目的是检查当用户名和口令验证失败后,LoginServlet是否会抛出RuntimeException,因此,response对象对测试没有影响,我们不需要模拟它,仅仅传入null即可。



最后,调用mc.verify()检查Mock对象是否按照预期的方法调用正常运行了。



运行JUnit,测试通过!表示我们的Mock对象正确工作了!



下一步,我们来测试当用户名和口令匹配时,LoginServlet应当把请求转发给指定的RequestDispatcher。在这个测试用例中,我们除了需要HttpServletRequest Mock对象外,还需要模拟ServletContext和RequestDispatcher对象:

Java代码 复制代码收藏代码
  1. MockControlrequestCtrl=MockControl.createControl(HttpServletRequest.class);
  2. HttpServletRequestrequestObj=(HttpServletRequest)requestCtrl.getMock();
  3. MockControlcontextCtrl=MockControl.createControl(ServletContext.class);
  4. finalServletContextcontextObj=(ServletContext)contextCtrl.getMock();
  5. MockControldispatcherCtrl=MockControl.createControl(RequestDispatcher.class);
  6. RequestDispatcherdispatcherObj=(RequestDispatcher)dispatcherCtrl.getMock();
  7. MockControlrequestCtrl=MockControl.createControl(HttpServletRequest.class);
  8. HttpServletRequestrequestObj=(HttpServletRequest)requestCtrl.getMock();
  9. MockControlcontextCtrl=MockControl.createControl(ServletContext.class);
  10. finalServletContextcontextObj=(ServletContext)contextCtrl.getMock();
  11. MockControldispatcherCtrl=MockControl.createControl(RequestDispatcher.class);
  12. RequestDispatcherdispatcherObj=(RequestDispatcher)dispatcherCtrl.getMock();
  13. 按照doPost()的语句顺序,我们设定Mock对象指定的行为:
  14. Java代码
  15. requestObj.getParameter("username");
  16. requestCtrl.setReturnValue("admin",1);
  17. requestObj.getParameter("password");
  18. requestCtrl.setReturnValue("123456",1);
  19. contextObj.getNamedDispatcher("dispatcher");
  20. contextCtrl.setReturnValue(dispatcherObj,1);
  21. dispatcherObj.forward(requestObj,null);
  22. dispatcherCtrl.setVoidCallable(1);
  23. requestCtrl.replay();
  24. contextCtrl.replay();
  25. dispatcherCtrl.replay();
  26. requestObj.getParameter("username");
  27. requestCtrl.setReturnValue("admin",1);
  28. requestObj.getParameter("password");
  29. requestCtrl.setReturnValue("123456",1);
  30. contextObj.getNamedDispatcher("dispatcher");
  31. contextCtrl.setReturnValue(dispatcherObj,1);
  32. dispatcherObj.forward(requestObj,null);
  33. dispatcherCtrl.setVoidCallable(1);
  34. requestCtrl.replay();
  35. contextCtrl.replay();
  36. dispatcherCtrl.replay();

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

Java代码 复制代码收藏代码
  1. LoginServletservlet=newLoginServlet(){
  2. publicServletContextgetServletContext(){
  3. returncontextObj;
  4. }
  5. };
  6. servlet.doPost(requestObj,null);
  7. LoginServletservlet=newLoginServlet(){
  8. publicServletContextgetServletContext(){
  9. returncontextObj;
  10. }
  11. };
servlet.doPost(requestObj, null);

最后,检查所有Mock对象的状态:

Java代码 复制代码收藏代码
  1. requestCtrl.verify();
  2. contextCtrl.verify();
  3. dispatcherCtrl.verify();
  4. requestCtrl.verify();
  5. contextCtrl.verify();
  6. 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 ...

完整的LoginServletTest代码如下:

Java代码
import javax.servlet.*;

import javax.servlet.http.*;

import org.easymock.*;

import junit.framework.TestCase;

public class LoginServletTest extends TestCase {

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

}

public void testLoginOK() throws Exception {

// create mock:

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

// set behavior:

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

// done!

requestCtrl.replay();

contextCtrl.replay();

dispatcherCtrl.replay();

// test:

LoginServlet servlet = new LoginServlet() {

public ServletContext getServletContext() {

return contextObj;

}

};

servlet.doPost(requestObj, null);

// verify:

requestCtrl.verify();

contextCtrl.verify();

dispatcherCtrl.verify();

}

}

import javax.servlet.*;

import javax.servlet.http.*;

import org.easymock.*;

import junit.framework.TestCase;

public class LoginServletTest extends TestCase {

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

}

public void testLoginOK() throws Exception {

// create mock:

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

// set behavior:

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

// done!

requestCtrl.replay();

contextCtrl.replay();

dispatcherCtrl.replay();

// test:

LoginServlet servlet = new LoginServlet() {

public ServletContext getServletContext() {

return contextObj;

}

};

servlet.doPost(requestObj, null);

// verify:

requestCtrl.verify();

contextCtrl.verify();

dispatcherCtrl.verify();

}

}
总结:

虽然EasyMock可以用来模仿依赖对象,但是,它只能动态模仿接口,无法模仿具体类。这一限制正好要求我们遵循“针对接口编程”的原则:如果不针对接口,则测试难于进行。应当把单元测试看作是运行时代码的最好运用,如果代码在单元测试中难于应用,则它在真实环境中也将难于应用。总之,创建尽可能容易测试的代码就是创建高质量的代码。

分享到:
评论

相关推荐

    easy mock.pdf

    方便快速上手使用easymock

    easy mock 3.1 API CHM

    Easy mock 3.1 API文档,CHM格式 如果打开无法显示,右键文件属性解除锁定后再尝试

    easy-mock-dev.zip

    压缩包中包括easy-mock-dev.zip、node-v12.10.0-linux-x64.tar.xz和centos7安装说明。 Centos部署node.js、MongoDB安装、Redis安装、部署easy-mock、导入SwaggerAPI文档

    如何使用Easy Mock视频教程

    如何使用Easy Mock视频教程 - 详情:https://blog.csdn.net/Dream_Weave/article/details/87891983

    easy mock 本地部署所有资源

    完整的easy mock 本地部署 window10,出坑资源啊,亲测成功,搭配安装教程https://blog.csdn.net/Embrace924/article/details/85699301

    easy-mock-cli:为Easy-Mock创建api.js。 https:easy-mock.github.ioeasy-mock-cli

    Easy Mock CLI是一个基于快速生成API调用文件的命令行工具。 如果您正在使用Easy Mock伪造接口数据,那一定不要错过Easy Mock CLI。 链接 安装 npm install -g easy-mock-cli 贡献 叉子! 创建功能分支:git ...

    easy-mock.zip

    包含node-v12.10.0-linux-x64.tar.xz , mongodb-linux-x86_64-rhel70-4.2.1.tgz , redis-3.0.0.tar.gz , easy-mock-dev.zip 等包

    dubbo-easy-mock:针对Dubbo接口的Mock解决方案

    这个框架的作用 在自动测试中,针对dubbo接口进行mock的框架 原理 利用dubbo的扩展点自动包装,通过EasyMockClusterWrapper... <artifactId>dubbo-easy-mock <version>1.1.0 <groupId>org.apache.dubbo</grou

    easy-mock-cli

    Easy Mock CLI 是一个基于 快速生成 API 调用文件的命令行工具。 如果你正在使用 Easy Mock 伪造接口数据,那一定不要错过 Easy Mock CLI。 Links Installation npm install -g easy-mock-cli License MIT

    EasyMock线上项目迁移工具

    Easy Mock 线上项目迁移工具

    TestMock的一个小案例代码

    TestMock的一个小案例代码 这是对一个servlet对一个小测试 有兴趣加QQ群:37424970 或联系本人MNS或邮箱:zhuseahui@yahoo.com.cn

    api-easy-mock

    更主要的是,后端这次给的接口,竟然只有一个,不不不,准确点说,是只有一个url请求,通过一个叫作code的参数字段来区分不同的接口:woman_shrugging: 不知道有没有人也遇到过这样的接口,反正我找了一圈没找到一个...

    2019年CFA一级mock打包.zip

    2019年CFA一级mock打包

    Easy Mock 详解

    EasyMock单元测试的扩展; EasyMock简介(抽象类接口做测试); EasyMock来进行测试; EasyMock如何打桩; EasyMock实践指南; EasyMock使用技巧; EasyMock使用简明手册; EasyMock使用说明;...用Mock object进行隔离测试;

    spring-mock.jar

    Classes contained in spring-mock.jar: org.springframework.mock.jndi.ExpectedLookupTemplate.class org.springframework.mock.jndi.SimpleNamingContext.class org.springframework.mock.jndi....

    mock2easyCopy

    mock2easymock2easy 是一款兼mock数据、接口自动化测试、接口地址重定向多功能于一身的工具,暂时提供 grunt、gulp 和 webpack 平台。尝试一下一定会帮你解决很大问题~基本配置options.portType: NumberDefault ...

    Mock是一个零侵入的服务端Mock平台,底层基于JVM Sandbox。相比于Fiddler、-mock-fe.zip

    Mock是一个零侵入的服务端Mock平台,底层基于JVM Sandbox。相比于Fiddler、-mock-fe

    easy-mock:tiny easy mock server,方便搭建本地、测试mock服务器

    快速搭建本地、测试环境Mock服务器安装 npm install easy-mock或者 git clone git@github.com:518yxx/easy-mock.git运行 node index.js使用访问在当前目录放入任意文件,访问 “127.0.0.1:4444/[文件名]” 即可。对于...

    基于python的mock测试数据练习

    mock练习

    EasyMock的安装与部署所需全部文件.zip

    windows环境部署easymock,有用到的全部文件和安装说明,包换node.js,redis,mongdb,easymock源码

Global site tag (gtag.js) - Google Analytics