zoukankan      html  css  js  c++  java
  • 聊聊单元測试(一)——EasyMock

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

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

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

    面对上述问题,有非常多开源项目对动态构建 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实现简单的用户验证的功能,若username和口令匹配“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 对象,这促使我们遵循“面向接口编程”的原则:假设不面向接口,则測试难于进行。是否easy进行单元測试也体现了代码质量的高低,难以測试的代码,通常也是充满坏味道的代码。能够这么说,假设代码在单元測试中难于应用,则它在真实环境中也将难于应用。总之,创建尽可能easy測试的代码就是创建高质量的代码。



  • 相关阅读:
    HDU5087——Revenge of LIS II(BestCoder Round #16)
    HDU5086——Revenge of Segment Tree(BestCoder Round #16)
    POJ3009——Curling 2.0(DFS)
    POJ2891——Strange Way to Express Integers(模线性方程组)
    算法总结之求解模线性方程组
    Linux运维学习笔记-网络技术知识体系总结
    Linux运维学习笔记-定时任务知识总结
    Linux运维学习笔记-文件权限知识总结
    Linux运维学习笔记-常用快捷键及vi、vim总结
    Linux运维学习笔记-角色知识总结
  • 原文地址:https://www.cnblogs.com/mfrbuaa/p/4281227.html
Copyright © 2011-2022 走看看