聊聊单元测试(一)——EasyMock

一、单元测试是保证软件质量的重要方法。

单元测试是对系统中某个模块功能的验证,但我们总会遇到这样那样的问题,导致测试代码很难编写。最直接的一个原因便是强耦合关系,被测试者依赖一些不容易构造,比较复杂的对象,如:如果要测试一个servlet,我们必须获得HttpServletRequest,甚至需要一个Web容器;如果要测试Dao层,我们可能要获得JDBC相关对象,最终获得ResultSet。这些对象的构建并不那么容易,如果我们使用Mock方法(常见的一种单元测试技术,它的主要作用是模拟一些在应用中不容易构造或者比较复杂的对象,从而把测试与测试边界以外的对象隔离开),编写自定义Mock对象是可以解决问题,但引入额外复杂代码的同时,很容易引入额外的错误。

二、发现的源动力就是不将就!

面对上述问题,有很多开源项目对动态构建 Mock 对象提供了支持,这些项目能够根据现有的接口或类动态生成Mock对象,这样不仅能避免额外的编码工作,同时也降低了引入错误的可能。

EasyMock 是一套用于通过简单的方法对于给定的接口生成 Mock 对象的类库。它提供对接口的模拟,能够通过录制、回放、检查三步来完成大体的测试过程,可以验证方法的调用种类、次数、顺序,可以令 Mock 对象返回指定的值或抛出指定异常。通过 EasyMock,我们可以方便的构造 Mock 对象从而使单元测试顺利进行。

三、使用EasyMock完成单元测试的过程大致可以划分为以下几个步骤:

  • 1、使用 EasyMock 生成 Mock 对象;
  • 2、设定 Mock 对象的预期行为和输出;
  • 3、将 Mock 对象切换到 Replay 状态;
  • 4、调用 Mock 对象方法进行单元测试;
  • 5、对 Mock 对象的行为进行验证。

四、文字表达有时候是苍白的,想不通过代码说事,还不行,看样子离大师还是有一段距离的。

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。

完整的LoginServletTest代码如下:

import javax.servlet.*;
import javax.servlet.http.*;
import org.easymock.*;

public class LoginServletTest {
  // 测试登陆失败
  @Test
   public void testLoginFailed() throws Exception {
        // 使用 EasyMock 生成 Mock 对象;
        MockControl mc = MockControl.createControl(HttpServletRequest.class);
        HttpServletRequest request = (HttpServletRequest)mc.getMock();
        // 设定 Mock 对象的预期行为和输出;
        request.getParameter("username");
        mc.setReturnValue("admin", 1);
        request.getParameter("password");
        mc.setReturnValue("1234", 1);
        // 将 Mock 对象切换到 Replay 状态;
        mc.replay();
        // now start test:
        LoginServlet servlet = new LoginServlet();
        try {
              // 里面会调用 Mock 对象方法进行单元测试;
            servlet.doPost(request, null);
            fail("Not caught exception!");
        }
        catch(RuntimeException re) {
            assertEquals("Login failed.", re.getMessage());
        }
        // 对 Mock 对象的行为进行验证。
        mc.verify();
    }
   
    // 测试登陆成功
    @Test
    public void testLoginOK() throws Exception {
        // 使用 EasyMock 生成 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();
       // 设定 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);
        // 将 Mock 对象切换到 Replay 状态;
        requestCtrl.replay();
        contextCtrl.replay();
        dispatcherCtrl.replay();
          // 里面会调用 Mock 对象方法进行单元测试;
        //为了让getServletContext()方法返回我们创建的ServletContext Mock对象,我们定义一个匿名类并覆写getServletContext()方法:
        LoginServlet servlet = new LoginServlet() {
            public ServletContext getServletContext() {
                return contextObj;
            }
        };
        servlet.doPost(requestObj, null);
         // 对 Mock 对象的行为进行验证。
        requestCtrl.verify();
        contextCtrl.verify();
        dispatcherCtrl.verify();
    }
}

五、总结

EasyMock 推荐根据指定接口动态构建 Mock 对象,这促使我们遵循“面向接口编程”的原则:如果不面向接口,则测试难于进行。是否容易进行单元测试也体现了代码质量的高低,难以测试的代码,通常也是充满坏味道的代码。可以这么说,如果代码在单元测试中难于应用,则它在真实环境中也将难于应用。总之,创建尽可能容易测试的代码就是创建高质量的代码。

聊聊单元测试(一)——EasyMock,布布扣,bubuko.com

时间: 2024-10-25 07:37:49

聊聊单元测试(一)——EasyMock的相关文章

聊聊单元测试(三)——Spring Test+JUnit完美组合

本着"不写单元测试的程序员不是好程序员"原则,我在坚持写着单元测试,不敢说所有的Java web应用都基于Spring,但至少一半以上都是基于Spring的. 发现通过Spring进行bean管理后,做测试会有各种不足, 例如,很多人做单元测试的时候,还要在Before方法中,初始化Spring容器,导致容器被初始化多次. [java] view plain copy print? @Before public void init() { ApplicationContext ctx 

如何搭建轻量级架构-代码组织篇

很多程序员看到标题,估计心里一愣:一个组织代码,有什么可讲的,无非是公司网址倒着写,外加命名规范,最多分模块管理而已!怎么这都能忽悠一篇文章来? 代码组织确实是一件简单的事情,但是如果我说的"代码组织"不仅仅限于这些内容呢... 大家都知道Web项目的架构,文件很琐碎.一个模块前台包含JS,CSS,HTML文件,后台还有模块的逻辑处理类,实体的数据库访问类,以及实体本身. 如果这个模块需要打印,还要有打印的模板文件! 如果这个模块还有一些关联数据,比如学员的学分数据等等. 算下来,一个

基于分布式、服务化的maven项目文件规划

引言 此文不是纯粹介绍maven概念,而是介绍一个具体的maven项目文件规划 这个规划可能适合于研发比较复杂的业务,这些业务有分布式和服务化的需要. 这个规划能够解决因为分布式和服务化要求而引起的项目繁多,项目混乱的问题. 与此同时这个规划也可以解决了在项目研发中出现的重复“轮子”的问题.这些“轮子”主要来源于两类: 代码的重复“轮子”,所以要抽取项目,导致项目数量进一步增多. 人工构建项目的重复“轮子”,构建的关系越来越复杂,错误率也越来越高,所以要通过基于配置和约定的方法来实现自动化构建.

java.lang.VerifyError 在使用PowerMock EasyMock进行单元测试

java.lang.VerifyError:Stack map does not match the one at exception handler 385 in method ... at offset 377 我使用的 jar包( 下面是认为跟这个问题有关系的jar包)如下: antrl-3.3-complete.jar cglib-2.2.jar cglib-nodep-2.2.2.jar easymock-3.2.jar mockito-all-1.9.5.jar persistent

Java单元测试进阶之如何打桩(用easymock轻松打桩)

打桩(mock)是单元测试的重要内容和难点,学好打桩的技术,做单元测试基本就没什么困难了. mock有两种,一种是静态的,一种是动态的.静态的就是在写测试代码之前根据需要打桩的类生成另外一个类,这个类就是mock object.动态的就是mock object是在测试代码运行的时候才生成的.所以很明显,动态打桩比静态打桩要方便地多.本章就是介绍动态打桩的工具. 早期的动态mock工具只能够mock接口,而不能够mock类:现在的mock工具无论是mock接口还是类都能够轻松完成了. easymo

C++雾中风景番外篇2:Gtest 与 Gmock,聊聊C++的单元测试

正式工作之后,公司对于单元测试要求比较严格.(笔者之前比较懒,一般很少写完整的单测~~).作为一个合格的开发工程师,需要为所编写代码编写适量的单元测试是十分必要的,在实际进行的开发工作之中,TDD(Test drivern development) 是一种经过实践可行的开发方式.编写单元测试可以帮助我们在开发阶段就发现错误,并且保证新的修改没有破坏已有的程序逻辑. 在 C++之中,常用的测试框架有 Gtest,Boost test,CPPUint 等.正是由于 Gmock 的加持,让 Gtest

系统测试——代码质量检查、单元测试、性能测试、自动构建、项目管理

根据软件开发的过程和由细节到外部的顺序,将软件测试划分为5个阶段: 1)代码质量检查:对代码的格式.潜在的Bug进行检查,常用的工具有Checkstyle.PMD.FindBugs: 2)单元测试:对代码的功能进行测试,常用的工具有JUnit.EasyMock: 3)性能测试:对代码的性能进行测试,常用的工具有JMeter.Profiler: 4)自动构建:对代码进行自动构建和持续集成测试.部署,常用的工具有Ant.Maven.CruiseControl: 5)项目管理:对软件测试中的Bug进行

使用 Spring 进行单元测试

单元测试和集成测试在我们的软件开发整个流程中占有举足轻重的地位,一方面,程序员通过编写单元测试来验证自己程序的有效性,另外一方面,管理者通过持续自动的执行单元测试和分析单元测试的覆盖率等来确保软件本身的质量.这里,我们先不谈单元测试本身的重要性,对于目前大多数的基于 Java 的企业应用软件来说,Spring 已经成为了标准配置,一方面它实现了程序之间的低耦合度,另外也通过一些配置减少了企业软件集成的工作量,例如和 Hibernate.Struts 等的集成.那么,有个问题,在普遍使用 Spri

EasyMock

JUnit是Java开发中常用的单元测试工具,对方法的测试很合适,但是一些情况下,JUnit就不是很适用了: 对象是一个复杂的对象,对象很难被创建,对象的某些行为很难触发 .这时可以使用Mock来创建对象进行测试,同时还可以Mock一个未实现的接口来进行测试. EasyMock是针对Java的Mock工具,通过 EasyMock,我们可以为指定的接口动态的创建 Mock 对象:     使用 EasyMock 生成 Mock 对象           设定 Mock 对象的预期行为和输出