转自:EasyMock使单元测试更加容易
单元测试是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类:
package com.jeffhanson.geronimo.jmx;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
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对象外,还需要模拟ServletContext和RequestDispatcher对象:
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 ...
完整的LoginServletTest代码如下:
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可以用来模仿依赖对象,但是,它只能动态模仿接口,无法模仿具体类。这一限制正好要求我们遵循“针对接口编程”的原则:如果不针对接
口,则测试难于进行。应当把单元测试看作是运行时代码的最好运用,如果代码在单元测试中难于应用,则它在真实环境中也将难于应用。总之,创建尽可能容易测
试的代码就是创建高质量的代码。
分享到:
相关推荐
在平台上,你可以创建自己的项目,每个项目代表一个独立的应用或服务。在项目中,你可以定义不同的接口来模拟不同的API。 3. **定义接口**: 创建接口是Easy Mock的核心操作。在接口页面,你需要定义接口的URL、...
方便快速上手使用easymock
Easy mock 3.1 API文档,CHM格式 如果打开无法显示,右键文件属性解除锁定后再尝试
压缩包中包括easy-mock-dev.zip、node-v12.10.0-linux-x64.tar.xz和centos7安装说明。 Centos部署node.js、MongoDB安装、Redis安装、部署easy-mock、导入SwaggerAPI文档
完整的easy mock 本地部署 window10,出坑资源啊,亲测成功,搭配安装教程https://blog.csdn.net/Embrace924/article/details/85699301
此外,它还提供了版本控制功能,方便团队协作,多人可以同时对同一个Mock服务进行操作,而不用担心数据冲突。 在实际应用中,Easy-Mock的使用场景非常广泛。比如,在前后端分离的项目中,前端开发可以通过调用Easy-...
Easy Mock CLI是一个基于快速生成API调用文件的命令行工具。 如果您正在使用Easy Mock伪造接口数据,那一定不要错过Easy Mock CLI。 链接 安装 npm install -g easy-mock-cli 贡献 叉子! 创建功能分支:git ...
包含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接口进行mock的框架 原理 利用dubbo的扩展点自动包装,通过EasyMockClusterWrapper... <artifactId>dubbo-easy-mock <version>1.1.0 <groupId>org.apache.dubbo</grou
Easy Mock CLI 是一个基于 快速生成 API 调用文件的命令行工具。 如果你正在使用 Easy Mock 伪造接口数据,那一定不要错过 Easy Mock CLI。 Links Installation npm install -g easy-mock-cli License MIT
Mock是一个强大的服务端模拟工具,它的核心特性是零侵入性,这意味着在你的实际项目代码中无需进行任何修改,即可实现服务的模拟。这极大地提高了开发效率,特别是在进行集成测试或者依赖于其他服务但又无法实时访问...
TestMock的一个小案例代码 这是对一个servlet对一个小测试 有兴趣加QQ群:37424970 或联系本人MNS或邮箱:zhuseahui@yahoo.com.cn
【标题】:“管理系统系列--vue后台管理系统 ,技术栈 vue2.0 +elementUi +vuex + easy-mock.zip”揭示了这是一个基于Vue.js框架的后台管理系统的实现,使用了Vue 2.0版本,Element UI组件库,Vuex状态管理器,以及...
为了解决这个问题,"本地搭建一个mock服务器方便调试开发" 成为了一个有效的策略。Mock服务器允许前端开发者在后端接口尚未完成时,模拟返回预期的数据,从而快速进行功能验证和界面开发,极大地提高了开发效率。 ...
更主要的是,后端这次给的接口,竟然只有一个,不不不,准确点说,是只有一个url请求,通过一个叫作code的参数字段来区分不同的接口:woman_shrugging: 不知道有没有人也遇到过这样的接口,反正我找了一圈没找到一个...
在编程中,当我们的代码依赖于其他不可控或难以配置的服务(如数据库、网络API等)时,Mock可以帮助我们创建一个虚拟的、可控制的替代品,这个替代品的行为和返回值是可以预设的。 描述中的链接指向了一篇关于Mock...
MockServer 是一个强大的工具,主要用于在开发过程中模拟服务器行为,特别是在接口开发和测试阶段。它允许开发者在没有实际后端服务的情况下,创建模拟的HTTP和HTTPS服务器,以便于快速验证前端代码或者自动化测试。...
在准备CFA一级考试的过程中,考生需要深入理解每一个科目的概念,尤其是定量方法中的统计和概率,财务报表分析中的会计原则,以及投资组合管理中的资产配置策略等。同时,考生还需要掌握如何在有限的时间内完成大量...
Python 提供了一个强大的库——`unittest.mock`,用于模拟(mock)对象、方法和类,以便在测试中隔离依赖关系,专注于测试目标代码的功能。本练习主要关注如何使用 `unittest.mock` 进行mock测试数据。 一、Mock...