JUnit介绍(转)

测试的重要性毋庸再说,但如何使测试更加准确和全面,而且独立于项目以外而且避免硬编码,JUnit给了咱们一个很好的解决方案。
1、引子
    首先假设有一个项目类SimpleObject以下:
    public class SimpleObject{
        public List methodA(){
             .....
        }
    }
    其中定义了一个methodA方法返回一个对象,好,如今咱们要对这个方法进行测试,看他是否是返回一个List对象,是否是为空,或者长度是否是符合标准等等。咱们写这样一个方法判断返回对象是否不为Null:
    public void assertNotNull(Object object){
        //判断object是否不为null
        ....
    }
    这个方法在JUnit框架中称之为一个断言,JUnit提供给咱们了不少断言,好比assertEqual,assertTrue...,咱们能够利用这些断言来判断两个值是否相等或者二元条件是否为真等问题。
    接下来咱们写一个测试类
    import junit.framework.*;
    public class TestSimpleObject extends TestCase{
        public TestSimpleObject(String name){
            super(name);
        }
        public void testSimple(){
            SimpleObject so=new SimpleObject();
            assertNotNull(so.methodA());
        }
    }
    而后咱们能够运行JUnit来检测咱们的测试结果,这样咱们在不影响Project文件的前提下,实现了对Project单元的测试。html

2、JUnit框架的结构
    经过前面的引子,其实咱们已经了解了JUnit基本的结构:
    一、import声明引入必须的JUnit类
    二、定义一个测试类从TestCase继承
    三、必需一个调用super(String)的构造函数
    四、测试类包含一些以test..开头的测试方法
    五、每一个方法包含一个或者多个断言语句
    固然还有一些其余的内容,但知足以上几条的就已是一个JUnit测试了java

3、JUnit的命名规则和习惯
    一、若是有一个名为ClassA的被测试函数,那么测试类的名称就是TestClassA
    二、若是有一个名为methodA的被测试函数,那么测试函数的名称就是testMethodAweb

4、JUnit自定义测试组合
    在JUnit框相架下,他会自动执行全部以test..开头的测试方法(利用java的反射机制),若是不想让他这么“智能”,一种方法咱们能够改变测试方法的名称,好比改为pendingTestMethodA,这样测试框架就会忽略它;第二种方法咱们能够本身手工组合咱们须要的测试集合,这个魔力咱们能够经过建立test suite来取得,任何测试类都可以包含一个名为suite的静态方法:
    public static Test suite();
    仍是以一个例子来讲明,假设咱们有两个名为TestClassOne、TestClassTwo的测试类,以下:
    import junit.framework.*;
    public class TestClassOne extends TestCase{
        public TestClassOne(String method){
            super(method);
        }
        public void testAddition(){
            assertEquals(4,2+2);
        }
        public void testSubtration(){
            assertEquals(0,2-2);
        }
    }数据库

    import junit.framework.*;
    public class TestClassTwo extends TestCase{
        public TestClassTwo(String method){
            super(method);
        } 
        public void testLongest(){
            ProjectClass pc=new ProjectClass();
            assertEquals(100,pc.longest());
        }
        public void testShortest(){
            ProjectClass pc=new ProjectClass();
            assertEquals(1,pc.shortest(10));
        }
        public void testAnotherShortest(){
            ProjectClass pc=new ProjectClass();
            assertEquals(2,pc.shortest(5));
        }
        public static Test suite(){
            TestSuite suite=new TestSuite();
            //only include short tests
            suite.addTest(new TestClassTwo("testShortest"));
            suite.addTest(new TestClassTwo("testAnotherShortest"));
        }
    }
    首先看TestClassTwo ,咱们经过suite显式的说明了咱们要运行哪些test方法,并且,此时咱们看到了给构造函数的String参数是作什么用的了:它让TestCase返回一个对命名测试方法的引用。接下来再写一个高一级别的测试来组合两个测试类:
    import junit.framework.*;
    public class TestClassComposite extends TestCase{
        public TestClassComposite(String method){
           super(method);
        }
        static public Test suite(){
           TestSuite suite = new TestSuite();
           //Grab everything 
           suite.addTestSuite(TestClassOne.class);
           //Use the suite method
           suite.addTest(TestClassTwo.suite());
           return suite;
        }
    }
    组合后的测试类将执行TestClassOne中的全部测试方法和TestClassTwo中的suite中定义的测试方法。
    
5、JUnit中测试类的环境设定和测试方法的环境设定
    每一个测试的运行都应该是互相独立的;从而就能够在任什么时候候,以任意的顺序运行每一个单独的测试。
    虽然这样是有好处的,但咱们若是在每一个测试方法里都写上相同的设置和销毁测试环境的代码,那显然是不可取的,好比取得数据库联接和关闭链接。好在JUnit的TestCase基类提供了两个方法供咱们改写,分别用于环境的创建和清理:
    protected void setUp();
    protected void tearDown();服务器

    一样道理,在某些状况下,咱们须要为整个test suite设置一些环境,以及在test suite中的全部方法都执行完成后作一些清理工做。要达到这种效果,咱们须要针对suite作一个setUp和tearDown,这可能稍微复杂一点,它须要提供所需的一个suite(不管经过什么样的方法)而且把它包装进一个TestSetup对象网络

    看下面这个例子:
    public class TestDB extends TestCase{
        private Connection dbConn;
        private String dbName;
        private String dbPort;
        private String dbUser;
        private String dbPwd;
        
        public TestDB(String method){
            super(method);
        }
        //Runs before each test method 
        protected void setUp(){
           dbConn=new Connection(dbName,dbPort,dbUser,dbPwd);
           dbConn.connect();
        }
        //Runs after each test method 
        protected void tearDown(){
           dbConn.disConnect();
           dbConn=null;
        }        
        public void testAccountAccess(){
           //Uses dbConn
           ....
        }        
        public void testEmployeeAccess(){
           //Uses dbConn
           ....
        }
        public static Test suite(){
           TestSuite suite=new TestSuite();
           suite.addTest(new TestDB("testAccountAccess"));
           suite.addTest(new TestDB("testEmployeeAccess"));
           TestSetup wrapper=new TestSetup(suite){
               protected void setUp(){
                  oneTimeSetUp();
               }
               protected void tearDown(){
                  oneTimeTearDown();
               }
           }
           return wrapper;
        }
        //Runs at start of suite
        public static void oneTimeSetUp(){
           //load properties of initialization
           //one-time initialize the dbName,dbPort...
        }
        //Runs at end of suite
        public static void oneTimeTearDown(){
           //one-time cleanup code goes here...
        }        
    }app

    上面这段代码的执行顺序是这样的:
    一、oneTimeSetUp()
    二、  setUp();
    三、    testAccountAccess();
    四、  tearDown();
    五、  setUp();
    六、    testEmployeeAccess();
    七、  tearDown();
    八、oneTimeTearDown();框架

6、自定义JUnit断言
    一般而言,JUnit所提供的标准断言对大多数测试已经足够了。然而,在某些环境下,咱们可能更须要自定义一些断言来知足咱们的须要。
    一般的作法是定义一个TestCase的子类,而且使用这个子类来知足全部的测试。新定义的共享的断言或者公共代码放到这个子类中。函数

7、测试代码的放置
    三种放置方式:
    一、同一目录——针对小型项目
       假设有一个项目类,名字为
       com.peiyuan.business.Account
       相应的测试位于
       com.peiyuan.business.TestAccount
       即物理上存在于同一目录
       
       优势是TestAccount可以访问Account的protected成员变量和函数
       缺点是测试代码处处都是,且堆积在产品代码的目录中测试

    二、子目录
       这个方案是在产品代码的目录之下建立一个test子目录
       同上,假设有一个项目类,名字为
       com.peiyuan.business.Account
       相应的测试位于
       com.peiyuan.business.test.TestAccount
      
       优势是能把测试代码放远一点,但又不置于太远
       缺点是测试代码在不一样的包中,因此测试类没法访问产品代码中的protected成员,解决的办法是写一个产品代码的子类来暴露那些成员。而后在测试代码中使用子类。
       
       举一个例子,假设要测试的类是这样的:
       package com.peiyuan.business;
       public class Pool{
           protected Date lastCleaned;
           ....
       }
       为了测试中得到non-public数据,咱们须要写一个子类来暴露它
       package com.peiyuan.business.test;
       import com.peiyuan.business.Pool;
       public class PoolForTesting extends Pool{
           public Date getLastCleaned(){
              return lastCleaned;
           }
           ....
       }
   
    三、并行树
       把测试类和产品代码放在同一个包中,但位于不一样的源代码树,注意两棵树的根都在编译器的CLASSPATH中。
       假设有一个项目类,位于
       prod/ com.peiyuan.business.Account
       相应的测试位于
       test/ com.peiyuan.business.TestAccount
       
       很显然这种作法继承了前两种的优势而摒弃了缺点,而且test代码至关独立

8、Mock的使用
    一、基础
       截至目前,前面提到的都是针对基本的java代码的测试,可是假若遇到这样的状况:某个方法依赖于其余一些难以操控的东西,诸如网络、数据库、甚至是servlet引擎,那么在这种测试代码依赖于系统的其余部分,甚至依赖的部分还要再依赖其余环节的状况下,咱们最终可能会发现本身几乎初始化了系统的每一个组件,而这只是为了给某一个测试创造足够的运行环境让他能够运行起来。这样不只仅消耗了时间,还给测试过程引入了大量的耦合因素。
       他的实质是一种替身的概念。
       举一个例子来看一下:假设咱们有一个项目接口和一个实现类。以下:
       public interface Environmental{
            public long getTime();
       }
       
       public class SystemEnvironment implements Environmental{
            public long getTime(){
               return System.currentTimeMillis();
            }
       }
       再有一个业务类,其中有一个依赖于getTime的新方法
       public class Checker{
            Environmental env;
            public Checker(Environmental anEnv){
               env=anEnv;
            }
            public void reminder(){
               Calendar cal = Calendar.getInstance();
               cal.setTimeInMillis(env.getTime());
               int hour =cal.get(Calendar.HOUR_OF_DAY);
               if(hour>=17){
                  ...... 
               }
            }
       }  
       由上可见,reminder方法依赖于getTime为他提供时间,程序逻辑实在下午5点以后进行提醒动做,但咱们作测试的时候不可能等到那个时候,因此就要写一个假的Environmental来提供getTime方法,以下:
       public class MockSystemEnvironment implements Environmental{
            private long currentTime;
            public long getTime(){
               return currentTime;
            }
            public void setTime(long aTime){
               currentTime= aTime;
            }
       }
       写测试的时候以这个类来替代SystemEnvironment就实现了替身的做用。

    二、MockObject
       接下来再看如何测试servlet,一样咱们须要一个web服务器和一个servlet容器环境的替身,按照上面的逻辑,咱们须要实现HttpServletRequest和HttpServletResponse两个接口。不幸的是一看接口,咱们有一大堆的方法要实现,呵呵,好在有人已经帮咱们完成了这个工做,这就是mockobjects对象。
import junit.framework.*;
import com.mockobjects.servlet.*;

public class TestTempServlet extends TestCase {
  public void test_bad_parameter() throws Exception {
     TemperatureServlet s = new TemperatureServlet();
     MockHttpServletRequest request =  new MockHttpServletRequest();
     MockHttpServletResponse response =  new MockHttpServletResponse();
     
     //在请求对象中设置参数
     request.setupAddParameter( "Fahrenheit", "boo!");
     //设置response的content type
     response.setExpectedContentType( "text/html");
     s.doGet(request,response);
     //验证是否响应
     response.verify();
     assertEquals("Invalid temperature: boo!\ n",
     response.getOutputStreamContents());
  }

  public void test_boil() throws Exception {
     TemperatureServlet s = new TemperatureServlet();
     MockHttpServletRequest request =
     new MockHttpServletRequest();
     MockHttpServletResponse response =
     new MockHttpServletResponse();
 
     request.setupAddParameter( "Fahrenheit", "212");
     response.setExpectedContentType( "text/html");
     s.doGet(request,response);
     response.verify();
     assertEquals("Fahrenheit: 212, Celsius: 100.0\ n",
     response.getOutputStreamContents());
  }
 
}
    三、EasyMock
    EasyMock采用“记录-----回放”的工做模式,基本使用步骤:
    * 建立Mock对象的控制对象Control。
    * 从控制对象中获取所须要的Mock对象。
    * 记录测试方法中所使用到的方法和返回值。
    * 设置Control对象到“回放”模式。
    * 进行测试。
    * 在测试完毕后,确认Mock对象已经执行了刚才定义的全部操做

    项目类:
package com.peiyuan.business;

import java.io.IOException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
 * <p>Title: 登录处理</p>
 * <p>Description: 业务类</p>
 * <p>Copyright: Copyright (c) 2006</p>
 * <p>Company: </p>
 * @author Peiyuan
 * @version 1.0
 */
public class LoginServlet extends HttpServlet {

    /* (非 Javadoc)
     * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    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.");
        }
    }
}

    测试类:
package com.peiyuan.business;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;


import org.easymock.MockControl;
import junit.framework.TestCase;
/**
 * <p>Title:LoginServlet测试类 </p>
 * <p>Description: 基于easymock1.2</p>
 * <p>Copyright: Copyright (c) 2006</p>
 * <p>Company: </p>
 * @author Peiyuan
 * @version 1.0
 */
public class LoginServletTest extends TestCase {

    /**     * 测试登录失败的状况     * @throws Exception     */    public void testLoginFailed() throws Exception {        //首先建立一个MockControl        MockControl mc = MockControl.createControl(HttpServletRequest.class);        //从控制对象中获取所须要的Mock对象        HttpServletRequest request = (HttpServletRequest)mc.getMock();        //“录制”Mock对象的预期行为        //在LoginServlet中,前后调用了request.getParameter("username")和request.getParameter("password")两个方法,        //所以,须要在MockControl中设置这两次调用后的指定返回值。        request.getParameter("username"); // 指望下面的测试将调用此方法,参数为"username"        mc.setReturnValue("admin", 1); // 指望返回值为"admin",仅调用1次        request.getParameter("password"); // 指望下面的测试将调用此方法,参数为" password"        mc.setReturnValue("1234", 1); // 指望返回值为"1234",仅调用1次        //调用mc.replay(),表示Mock对象“录制”完毕        mc.replay();        //开始测试        LoginServlet servlet = new LoginServlet();        try {            //因为本次测试的目的是检查当用户名和口令验证失败后,LoginServlet是否会抛出RuntimeException,            //所以,response对象对测试没有影响,咱们不须要模拟它,仅仅传入null便可。            servlet.doPost(request, null);            fail("Not caught exception!");        }        catch(RuntimeException re) {            assertEquals("Login failed.", re.getMessage());        }        // verify:        mc.verify();    }        /**     * 测试登录成功的状况     * @throws Exception     */    public void testLoginOK() throws Exception {         //首先建立一个request的MockControl        MockControl requestCtrl = MockControl.createControl(HttpServletRequest.class);          //从控制对象中获取所须要的request的Mock对象        HttpServletRequest requestObj = (HttpServletRequest)requestCtrl.getMock();         //建立一个ServletContext的MockControl        MockControl contextCtrl = MockControl.createControl(ServletContext.class);        //从控制对象中获取所须要的ServletContext的Mock对象        final ServletContext contextObj = (ServletContext)contextCtrl.getMock();        //建立一个RequestDispatcher的MockControl        MockControl dispatcherCtrl = MockControl.createControl(RequestDispatcher.class);        //从控制对象中获取所须要的RequestDispatcher的Mock对象        RequestDispatcher dispatcherObj = (RequestDispatcher)dispatcherCtrl.getMock();        requestObj.getParameter("username"); // 指望下面的测试将调用此方法,参数为"username"        requestCtrl.setReturnValue("admin", 1); // 指望返回值为"admin",仅调用1次        requestObj.getParameter("password"); // 指望下面的测试将调用此方法,参数为" password"        requestCtrl.setReturnValue("123456", 1); // 指望返回值为"1234",仅调用1次        contextObj.getNamedDispatcher("dispatcher");        contextCtrl.setReturnValue(dispatcherObj, 1);        dispatcherObj.forward(requestObj, null);        dispatcherCtrl.setVoidCallable(1);        requestCtrl.replay();        contextCtrl.replay();        dispatcherCtrl.replay();        //为了让getServletContext()方法返回咱们建立的ServletContext Mock对象,        //咱们定义一个匿名类并覆写getServletContext()方法        LoginServlet servlet = new LoginServlet() {            public ServletContext getServletContext() {                return contextObj;            }        };        servlet.doPost(requestObj, null);    }}

相关文章
相关标签/搜索