慎用setActionForm() 在使用strutstestcase测试struts程序时

setActionForm()方法会调用ActionForm里的reset()方法清掉你传入actionForm中的测试数据。

如:

   EmailResetPsdForm form = new EmailResetPsdForm();

  form.setUserName("ppig");

  setActionForm(form);

在这里,测试数据userName="ppig"不会被传入使用这个form bean的action中。

应该使用替代的方法来传递测试数据:

addRequestParameter("userName", "ppig");

下面是strutstestcase里的文档对setActionForm方法的说明。

    /*

     * setActionForm

     * public void setActionForm(ActionForm form)Sets an ActionForm instance to be used in this

     * test. The given ActionForm instance will be stored in the scope specified in the Struts

     * configuration file (ie: request or session). Note that while this ActionForm instance

     * is passed to the test, Struts will still control how it is used. In particular, it

     * will call the ActionForm.reset() method, so if you override this method in your

     * ActionForm subclass, you could potentially reset attributes in the form passed

     * through this method.

     *

     * Parameters:

     * form - the ActionForm instance to be used in this test.

     *

     */

Junit不能用于多线程的测试吗?

  我在为(改善学习日记的登录模式 (0篇) http://www.learndiary.com/disGoalContentAction.do?goalID=1214&naviStr=a10a60)写发送重置密码信件的测试代码时,发现测试过程中,测试代码中另一个发送邮件的线程没有执行,测试程序就结束了。

  难道junit不能用于多线程的程序的测试吗?

  

  这是含发送邮件的程序:

// $Id: EmailResetPsdAction.java,v 1.8 2005/12/19 16:00:49 dashing_meng Exp $

// Copyright (c) 2004-2005 Http://www.learndiary.com. All Rights Reserved.

// Permission to use, copy, modify, and distribute this software and its

// documentation without fee, and without a written agreement is hereby

// granted, provided that the above copyright notice and this paragraph

// appear in all copies.  This software program and documentation are

// copyrighted by http://www.learndiary.com. The software program and

// documentation are supplied "AS IS", without any accompanying services

// from The LearnDiary. The LearnDiary does not warrant that the operation

// of the program will be uninterrupted or error-free. The end-user

// understands that the program was developed for research purposes and is

// advised not to rely exclusively on the program for any reason.

// IN NO EVENT SHALL HTTP://WWW.LEARNDIARY.COM BE LIABLE TO ANY PARTY FOR

// DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,

// INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS

// DOCUMENTATION, EVEN IF HTTP://WWW.LEARNDIARY.COM HAS BEEN ADVISED OF THE

// POSSIBILITY OF SUCH DAMAGE. HTTP://WWW.LEARNDIARY.COM SPECIFICALLY

// DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED

// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE

// SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND

// HTTP://WWW.LEARNDIARY.COM HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE,

// SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.

package com.learndiary.website.action.account;

import java.sql.Timestamp;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.logging.*;

import org.apache.struts.action.ActionForward;

import org.apache.struts.action.Action;

import org.apache.struts.action.ActionMapping;

import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.ActionForm;

import com.learndiary.website.actionform.EmailResetPsdForm;

import com.learndiary.website.manager.EmailResetPsdManager;

import com.learndiary.website.manager.EmailSender;

import com.learndiary.website.manager.UserManager;

import com.learndiary.website.model.Email;

import com.learndiary.website.model.EmailResetPsdInfo;

import com.learndiary.website.model.UserInfo;

import com.learndiary.website.util.Util;

/**

 * Process user's asking for sending resetting password token email request.

 * @author http://www.learndiary.com,LearnDiary Develop Group

 */

public class EmailResetPsdAction extends Action {

  /**

   *  Function:

   *  Process user's asking for sending resetting password token email request.

   *  Pseudo Coding:

   * 

   *  {

   *    String userName;

   *    UserInfo userInfo;

   *    int userID;

   *    UserManager userManager;

   *    EmailResetPsdManager emailResetPsdManager;

   *    EmailResetPsdInfo emailResetPsdInfo;

   *    Email email;

   * 

   *    userName=((EmailResetPsdForm)form).getUserName();//Get the userName from form

   *    userInfo= userManager. findByName(userName);//Get userInfo

   *    if (userInfo==null){

   *      forward to emailFailure.jsp;

   *    }

   *    userID=userInfo.getUserID();

   *    emailResetPsdInfo=emailResetPsdManager.findByID();// Get emailResetPsdInfo

   *    if (emailResetPsdInfo!=null){

   *      foward to emailFailure.jsp;

   *    }

   * 

   *    //set emaiResetPsdInfo

   *    emaiResetPsdInfo.setUserID(userID);

   *    emaiResetPsdInfo.setToken(com.learndiary.website.util.Util.genRandomStr());

   *    emailResetPsdInfo.setSendDate(System.getCurrentTime());

   *    emailResetPsdManager.insertResetPsdInfo(emailResetPsdInfo);//record emailResetPsdInfo

   *    //set reset passwrod email's content

   *    email.setRecipient(userInfo.getEmail());

   *    email.setSubject("your reset password request.");

   *    email.setText( "http://localhost:8080/learndiary/resetPsdAction.do?userID=" + userID + "token=" + emaiResetPsdInfo.getToken());

   *    emailSender.send(email);

   *    forward to emailSuccess.jsp;

   *  }

   * 

   * 

   *   

   */

    private org.apache.commons.logging.Log __log = LogFactory.getFactory().getInstance(this.getClass());

  public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) {

String myaction = mapping.getPath(); 

    __log.info("Enter action: "+myaction);

    String target=null;

    String userName=null;

    UserInfo userInfo=null;

    int userID;

    UserManager userManager=new UserManager();

    EmailResetPsdManager emailResetPsdManager=new EmailResetPsdManager();

    EmailResetPsdInfo emailResetPsdInfo=null;

    Email email=null;

    EmailSender emailSender=new EmailSender();

   

    userName=((EmailResetPsdForm)form).getUserName();//Get the userName from form

    try {

__log.debug("Before findByName,and name is: "+userName);

        userInfo= userManager.findByName(userName);//Get userInfo

__log.debug("After findByName,and userInfo is: "+userInfo);

    } catch (ClassNotFoundException e1) {

e1.printStackTrace();

    }

    if (userInfo==null){

      target=new String("failure");

  return (mapping.findForward(target));//no such a user,forward to emailFailure.jsp;

    }

    userID=userInfo.getUserID();

    __log.debug("userID: "+userID);

    try {

        emailResetPsdInfo=emailResetPsdManager.findByID(userID);// Get emailResetPsdInfo

    } catch (ClassNotFoundException e) {

e.printStackTrace();

    }

   

    __log.debug("existed emailResetPsdInfo is: " + emailResetPsdInfo);

    if (emailResetPsdInfo!=null){//user requested emailing resetting password token in 72 hours

      target=new String("failure");

  return (mapping.findForward(target));//foward to emailFailure.jsp;

    }

 

    //set emaiResetPsdInfo

emailResetPsdInfo=new EmailResetPsdInfo();

    emailResetPsdInfo.setUserID(userID);

    emailResetPsdInfo.setToken(Util.genRandomStr());

    emailResetPsdInfo.setSendTime(new Timestamp(System.currentTimeMillis()));

    __log.debug("new emailResetPsdInfo is: "+emailResetPsdInfo);

    try {

      emailResetPsdManager.insertResetPsdInfo(emailResetPsdInfo);

      __log.debug("insert emailResetPsdInfo ok!");

    } catch (Exception e2) {

      e2.printStackTrace();

    }//record emailResetPsdInfo

    //set reset passwrod email's content

    email=new Email();

    email.setRecipient(userInfo.getEmail());

    email.setSubject("Your resetting password request.");

    email.setText( "http://localhost:8080/learndiary_login/account/resetPsd.jsp?userID="

      + userID + "&token=" + emailResetPsdInfo.getToken());

    __log.debug("email is: " + email); 

    emailSender.send(email);//这里要产生另一个线程发送邮件,但在测试程序中没有执行,在正常运行时会执行

   

    target=new String("success");

return (mapping.findForward(target));//email resetting password token success,

                                     //forward to emailSuccess.jsp;

  }

 

  /**

   * process general error

   * @param e error object

  

  private void generalError(Exception e) {

    e.printStackTrace();

    __log.error(" [EmailResetPsd] Error - " + e.getMessage());

  }*/

}

这时对应的测试程序:

//$Id: EmailResetPsdActionTest.java,v 1.3 2005/12/19 16:00:49 dashing_meng Exp $

//Copyright (c) 2004-2005 Http://www.learndiary.com. All Rights Reserved.

//Permission to use, copy, modify, and distribute this software and its

//documentation without fee, and without a written agreement is hereby

//granted, provided that the above copyright notice and this paragraph

//appear in all copies.  This software program and documentation are

//copyrighted by http://www.learndiary.com. The software program and

//documentation are supplied "AS IS", without any accompanying services

//from The LearnDiary. The LearnDiary does not warrant that the operation

//of the program will be uninterrupted or error-free. The end-user

//understands that the program was developed for research purposes and is

//advised not to rely exclusively on the program for any reason.

//IN NO EVENT SHALL HTTP://WWW.LEARNDIARY.COM BE LIABLE TO ANY PARTY FOR

//DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,

//INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS

//DOCUMENTATION, EVEN IF HTTP://WWW.LEARNDIARY.COM HAS BEEN ADVISED OF THE

//POSSIBILITY OF SUCH DAMAGE. HTTP://WWW.LEARNDIARY.COM SPECIFICALLY

//DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED

//WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE

//SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND

//HTTP://WWW.LEARNDIARY.COM HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE,

//SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.

package com.learndiary.website.action.account;

import java.sql.Timestamp;

import org.apache.commons.logging.Log;

import org.apache.commons.logging.LogFactory;

import servletunit.struts.MockStrutsTestCase;

import com.learndiary.website.actionform.EmailResetPsdForm;

import com.learndiary.website.dao.EmailResetPsdDAO;

import com.learndiary.website.dao.TransContext;

import com.learndiary.website.dao.UserDAO;

import com.learndiary.website.model.EmailResetPsdInfo;

import com.learndiary.website.model.UserInfo;

/**

 * test EmailResetPsdAction

 * @author http://www.learndiary.com, LearnDiary Develop Group

 */

public class EmailResetPsdActionTest extends MockStrutsTestCase {

Log log = LogFactory.getLog(EmailResetPsdActionTest.class.getName());

private transient TransContext globalTran = null;

UserDAO userDAO=null;

EmailResetPsdDAO emailResetPsdDAO=null;

//the contextDir can be commented by adding classpath:${web} into build.xml

//String contextDir = "E:\zhangwei\eclipse_workspace1\learndiary_login\web";

public EmailResetPsdActionTest(String testName) {

super(testName);

}

   

    /**

     * empty table "user" and "emailresetpsd";

     * insert two user:"ppig" and "tom" into table user,

     * "ppig" to test successful case,

     * "tom" to test duplicate request failure case;

     * insert a record into table emailresetpsd for

     * "tom" to test duplicate request failure case.

     * @see junit.framework.TestCase#setUp()

     */

public void setUp() throws Exception {

super.setUp();

//this.setContextDirectory(new File(contextDir));

log.debug("ahah");

globalTran = new TransContext();

userDAO = new UserDAO(globalTran);

emailResetPsdDAO=new EmailResetPsdDAO(globalTran);

//empty two tables

userDAO.deleteAll();

        emailResetPsdDAO.deleteAll();

       

        UserInfo info1=new UserInfo();//for testing successful request

info1.setUserName("ppig");

info1.setPsd("123456");

info1.setEmail("learndiary@126.com");

userDAO.insertObject(info1);

UserInfo info2=new UserInfo();//for testing duplicate request failure

info2.setUserName("tom");

info2.setPsd("223456");

info2.setEmail("tom@tom.com");

userDAO.insertObject(info2);

        int userID2=((UserInfo)userDAO.findByName("tom")).getUserID();

        

EmailResetPsdInfo emailResetPsdInfo=new EmailResetPsdInfo();

emailResetPsdInfo.setUserID(userID2);

emailResetPsdInfo.setToken("12345678");

emailResetPsdInfo.setSendTime(new Timestamp(System.currentTimeMillis()-1000*60*2));

emailResetPsdDAO.insertObject(emailResetPsdInfo);

}

   

    /**

     * empty table "user" and table "emailresetpsd"

     * @see junit.framework.TestCase#tearDown()

     */

public void tearDown() throws Exception {

super.tearDown();

        userDAO.deleteAll();

        emailResetPsdDAO.deleteAll();

}

/**

* request emailing resetting password token successfully

*/

public void testSuccess() {

// a valid request

setRequestPathInfo("/emailResetPsdAction");

EmailResetPsdForm form = new EmailResetPsdForm();

form.setUserName("ppig");

setActionForm(form);

actionPerform();

verifyForward("success");

// verifyForwardPath("/success.jsp");

// assertEquals("deryl",getSession().getAttribute("authentication"));

verifyNoActionErrors();

}

    /**

     *  request emailing resetting password token failure

     *

     */

public void testFailure() {

// fail when duplicate request

EmailResetPsdForm form = new EmailResetPsdForm();

form.setUserName("tom");

setRequestPathInfo("/emailResetPsdAction");

setActionForm(form);

actionPerform();

verifyForward("failure");

// fail when user not found

form = new EmailResetPsdForm();

form.setUserName("000000000000000");

setRequestPathInfo("/emailResetPsdAction");

setActionForm(form);

actionPerform();

verifyForward("failure");

verifyNoActionErrors();

}

public static void main(String[] args) {

junit.textui.TestRunner.run(EmailResetPsdActionTest.class);

}

}

呵呵,测试了一下新买的浴霸(非程序:))

  新买某种品牌的浴霸,说明书称:出厂经4。C的冰水测试通过。

  这两天在学测试程序,为了验证这个浴霸的安全性,我对它测试了一番。用还是热的水泼在取暖泡上,哧的一响,经受了考验,再来一下,在另一个取暖泡上测试。结果取暖泡里面冒烟了。表面也有裂纹了。失败。

  看来,出厂的测试是夸大其辞呀。

  老婆骂我。。。

  我只是告诉她,经过测试,这个浴霸不能被淋浴头淋到。

  浴霸真的不能被水淋吗?还是我们这个浴霸本身就有问题?还是测试过度了。我自己也糊涂了。。。

在XP下强调单元测试必须由类包的编写者负责编写

转自:http://www-128.ibm.com/developerworks/cn/java/l-junit/

怎样使用Junit Framework进行单元测试的编写 

 

  文档选项

   将此页作为电子邮件发送

 

 

对此页的评价

  帮助我们改进这些内容

 

 

 

级别: 初级

关于作者艾昂科技上海公司

2002 年 7 月 01 日

随着Refactoring技术和XP软件工程技术的广泛推广,单元测试的作用在软件工程中变得越来越重要,而一个简明易学、适用广泛、高效稳定的单元测试框架则对成功的实施单元测试有着至关重要的作用。在java编程语句环境里,Junit Framework是一个已经被多数java程序员采用和实证的优秀的测试框架,但是多数没有尝试Junit Framework的程序员在学习如何Junit Framework来编写适应自己开发项目的单元测试时,依然觉得有一定的难度,这可能是因为Junit随框架代码和实用工具附带的用户指南和文档的着重点在于解释单元测试框架的设计方法以及简单的类使用说明,而对在特定的测试框架(Junit)下如何实施单元测试,如何在项目开发的过程中更新和维护已经存在的单元测试代码没有详细的解释。因此本文档就两个着重点对Junit所附带的文档进行进一步的补充和说明,使Junit能被更多的开发团队采用,让单元测试乃至Refactoring、XP技术更好在更多的开发团队中推广。

1. 单元测试的编写原则

Junit附带文档所列举的单元测试带有一定的迷惑性,因为几乎所有的示例单元都是针对某个对象的某个方法,似乎Junit的单元测试仅适用于类组织结构的静态约束,从而使初学者怀疑Junit下的单元测试所能带来的效果。因此我们需要重新定义如何确定有价值的单元测试以及如何编写这些单元测试、维护这些单元测试,从而让更多的程序员接受和熟悉Junit下的单元测试的编写。

在Junit单元测试框架的设计时,作者一共设定了三个总体目标,第一个是简化测试的编写,这种简化包括测试框架的学习和实际测试单元的编写;第二个是使测试单元保持持久性;第三个则是可以利用既有的测试来编写相关的测试。从这三个目标可以看出,单元测试框架的基本设计考虑依然是从我们现有的测试方式和方法出发,而只是使测试变得更加容易实施和扩展并保持持久性。因此编写单元测试的原则可以从我们通常使用的测试方法借鉴和利用。

 

 回页首

 

2. 如何确定单元测试

在我们通常的测试中,一个单元测试一般针对于特定对象的一个特定特性,譬如,假定我们编写了一个针对特定数据库访问的连接池的类包实现,我们会建立以下的单元测试:

在连接池启动后,是否根据定义的规则在池中建立了相应数量的数据库连接

申请一个数据库连接,是否根据定义的规则从池中直接获得缓存连接的引用,还是建立新的连接

释放一个数据库连接后,连接是否根据定义的规则被池释放或者缓存以便以后使用

后台Housekeeping线程是否按照定义的规则释放已经过期的连接申请

如果连接有时间期限,后台Housekeeping线程是否定期释放已经过期的缓存连接

这儿只列出了部分的可能测试,但是从这个列表我们可以看出单元测试的粒度。一个单元测试基本是以一个对象的明确特性为基础,单元测试的过程应该限定在一个明确的线程范围内。根据上面所述,一个单元测试的测试过程非常类似于一个Use Case的定义,但是单元测试的粒度一般来说比Use Case的定义要小,这点是容易理解的,因为Use Case是以单独的事务单元为基础的,而单元测试是以一组聚合性很强的对象的特定特征为基础的,一般而言一个事务中会利用许多的系统特征来完成具体的软件需求。

从上面的分析我们可以得出,测试单元应该以一个对象的内部状态的转换为基本编写单元。一个软件系统就和一辆设计好的汽车一样,系统的状态是由同一时刻时系统内部的各个分立的部件的状态决定的,因此为了确定一个系统最终的行为符合我们起始的要求,我们首先需要保证系统内的各个部分的状态会符合我们的设计要求,所以我们的测试单元的重点应该放在确定对象的状态变换上。

然而需要注意的并不是所有的对象组特征都需要被编写成独立的测试单元,如何在对象组特征里筛选有价值的测试单元的原则在JUnitTest Infected: Programmers Love Writing Tests一文中得到了正确的描述,你应该在有可能引入错误的地方引入测试单元,通常这些地方存在于有特定边界条件、复杂算法以及需求变动比较频繁的代码逻辑中。除了这些特性需要被编写成独立的测试单元外,还有一些边界条件比较复杂的对象方法也应该被编写成独立的测试单元,这部分单元测试已经在Junit文档中被较好的描述和解释过了。

在基本确定了需要编写的单元测试,我们还应该问自己:编写好了这些测试,我们是否可以有把握地告诉自己,如果代码通过了这些单元测试,我们能认定程序的运行是正确的,符合需求的。如果我们不能非常的确定,就应该看看是否还有遗漏的需要编写的单元测试或者重新审视我们对软件需求的理解。通常来说,在开始使用单元测试的时候,更多的单元测试总是没有错的。

一旦我们确定了需要被编写的测试单元,接下来就应该

 

 回页首

 

3. 如何编写单元测试

在XP下强调单元测试必须由类包的编写者负责编写,这个限定对于我们设定的测试目标是必须的。因为只有这样,测试才能保证对象的运行时态行为符合需求,而仅通过类接口的测试,我们只能确保对象符合静态约束,因此这就要求我们在测试的过程中,必须开放一定的内部数据结构,或者针对特定的运行行为建立适当的数据记录,并把这些数据暴露给特定的测试单元。这也就是说我们在编写单元测试时必须对相应的类包进行修改,这样的修改也发生在我们以前使用的测试方法中,因此以前的测试标记及其他一些测试技巧仍然可以在Junit测试中改进使用。

由于单元测试的总体目标是负责我们的软件在运行过程中的正确无误,因此在我们对一个对象编写单元测试的时候,我们不但需要保证类的静态约束符合我们的设计意图,而且需要保证对象在特定的条件下的运行状态符合我们的预先设定。还是拿数据库缓冲池的例子说明,一个缓冲池暴露给其他对象的是一组使用接口,其中包括对池的参数设定、池的初始化、池的销毁、从这个池里获得一个数据连接以及释放连接到池中,对其他对象而言随着各种条件的触发而引起池的内部状态的变化是不需要知道的,这一点也是符合封装原理的。但是池对象的状态变化,譬如:缓存的连接数在某些条件下会增长,一个连接在足够长的运行后需要被彻底释放从而使池的连接被更新等等,虽然外部对象不需要明确,但是却是程序运行正确的保证,所以我们的单元测试必须保证这些内部逻辑被正确的运行。

编译语言的测试和调试是很难对运行的逻辑过程进行跟踪的,但是我们知道,无论逻辑怎么运行,如果状态的转换符合我们的行为设定,那验证结果显然是正确的,因此在对一个对象进行单元测试的时候,我们需要对多数的状态转换进行分析和对照,从而验证对象的行为。状态是通过一系列的状态数据来描述的,因此编写单元测试首先分析出状态的变化过程(状态转换图对这个过程的描述非常清晰),然后根据状态的定义确定分析的状态数据,最后是提供这些内部的状态数据的访问。在数据库连接池的例子中,我们对池实现的对象DefaultConnectionProxy的状态变换进行分析后,我们决定把表征状态的OracleConnectionCacheImpl对象公开给测试类。参见示例一

示例一

/**

 * 这个类简单的包装了oracle对数据连接缓冲池的实现。

 *

*/

public class DefaultConnectionProxy extends ConnectionProxy {

private static final String name = "Default Connection Proxy";

private static final String description = "这个类简单的包装了oracle对数据连接缓冲池的实现。";

private static final String author = "Ion-Global.com";

private static final int major_version = 0;

private static final int minor_version = 9;

private static final boolean pooled = true;

  private ConnectionBroker connectionBroker = null;

private Properties props;

private Properties propDescriptions;

private Object initLock = new Object();

// Test Code Begin...

/* 为了能够了解对象的状态变化,因此需要把表征对象内部状态变化的部分私有变量提供公共的访问接口

   (或者提供让同一个类包的访问接口),以便使测试单元可以有效地判断对象的状态转变,

    在本示例中对包装的OracleConnectionCacheImpl对象提供访问接口。

*/

OracleConnectionCacheImpl getConnectionCache() {

if (connectionBroker == null) {

throw new IllegalStateException("You need start the server first.");

}

return connectionBroker.getConnectionCache();

}

// Test Code End...

在公开内部状态数据後,我们就可以编写我们的测试单元了,单元测试的选择方法和选择尺度已经在本文前面章节进行了说明,

但是仍然需要注意的是,由于assert方法会抛出一个error,你应该在测试方法的最后集中用assert相关方法进行判断,

这样可以确保资源得到释放。

对数据库连接池的例子,我们可以建立测试类DefaultConnectionProxyTest,同时建立数个test case,如下

示例二

/**

 * 这个类对示例一中的类进行简单的测试。

 *

*/

public class DefaultConnectionProxyTest extends TestCase {

private DefaultConnectionProxy conProxy = null;

private OracleConnectionCacheImpl cacheImpl = null;

private Connection con = null;

/** 设置测试的fixture,建立必要的测试起始环境。

*/

protected void setUp() {

conProxy = new DefaultConnectionProxy();

conProxy.start();

cacheImpl = conProxy.getConnectionCache();

}

/** 对示例一中的对象进行服务启动后的状态测试,检查是否在服务启动后,

连接池的参数设置是否正确。

*/

public void testConnectionProxyStart() {

int minConnections = 0;

int maxConnections = 0;

assertNotNull(cacheImpl);

try {

minConnections = Integer.parseInt(PropertyManager.getProperty

("DefaultConnectionProxy.minConnections"));

maxConnections = Integer.parseInt(PropertyManager.getProperty

("DefaultConnectionProxy.maxConnections"));

} catch (Exception e) {

// ignore the exception

}

assertEquals(cacheImpl.getMinLimit(), minConnections);

assertEquals(cacheImpl.getMaxLimit(), maxConnections);

assertEquals(cacheImpl.getCacheSize(), minConnections);

}

/** 对示例一中的对象进行获取数据库连接的测试,看看是否可以获取有效的数据库连接,

并且看看获取连接后,连接池的状态是否按照既定的策略进行变化。由于assert方法抛出的是

error对象,因此尽可能把assert方法放置到方法的最后集体进行测试,这样在方法内打开的

资源,才能有效的被正确关闭。

*/

public void testGetConnection() {

int cacheSize = cacheImpl.getCacheSize();

int activeSize = cacheImpl.getActiveSize();

int cacheSizeAfter = 0;

int activeSizeAfter = 0;

con = conProxy.getConnection();

if (con != null) {

activeSizeAfter = cacheImpl.getActiveSize();

cacheSizeAfter = cacheImpl.getCacheSize();

try {

con.close();

} catch (SQLException e) {

}

} else {

assertNotNull(con);

}

/*如果连接池中的实际使用连接数小于缓存连接数,检查获取的新的数据连接是否

从缓存中获取,反之连接池是否建立新的连接

*/

if (cacheSize > activeSize) {

assertEquals(activeSize + 1, activeSizeAfter);

assertEquals(cacheSize, cacheSizeAfter);

} else {

assertEquals(activeSize + 1, cacheSizeAfter);

}

}

/** 对示例一中的对象进行数据库连接释放的测试,看看连接释放后,连接池的

状态是否按照既定的策略进行变化。由于assert方法抛出的是error对象,因此尽可

能把assert方法放置到方法的最后集体进行测试,这样在方法内打开的

资源,才能有效的被正确关闭。

*/

public void testConnectionClose() {

int minConnections = cacheImpl.getMinLimit();

int cacheSize = 0;

int activeSize = 0;

int cacheSizeAfter = 0;

int activeSizeAfter = 0;

con = conProxy.getConnection();

if (con != null) {

cacheSize = cacheImpl.getCacheSize();

activeSize = cacheImpl.getActiveSize();

try {

con.close();

} catch (SQLException e) {

}

activeSizeAfter = cacheImpl.getActiveSize();

cacheSizeAfter = cacheImpl.getCacheSize();

} else {

assertNotNull(con);

}

assertEquals(activeSize, activeSizeAfter + 1);

/*如果连接池中的缓存连接数大于最少缓存连接数,检查释放数据连接后是否

缓存连接数比之前减少了一个,反之缓存连接数是否保持为最少缓存连接数

*/

if (cacheSize > minConnections) {

assertEquals(cacheSize, cacheSizeAfter + 1);

} else {

assertEquals(cacheSize, minConnections);

}

}

/** 释放建立测试起始环境时的资源。

*/

protected void tearDown() {

cacheImpl = null;

conProxy.destroy();

}

public DefaultConnectionProxyTest(String name) {

super(name);

}

/** 你可以简单的运行这个类从而对类中所包含的测试单元进行测试。

*/

public static void main(String args[]) {

junit.textui.TestRunner.run(DefaultConnectionProxyTest.class);

}

}

 

当单元测试完成后,我们可以用Junit提供的TestSuite对象对测试单元进行组织,你可以决定测试的顺序,然后运行你的测试。

 

 回页首

 

4. 如何维护单元测试

通过上面的描述,我们对如何确定和编写测试有了基本的了解,但是需求总是变化的,因此我们的单元测试也会根据需求的变化不断的演变。如果我们决定修改类的行为规则,可以明确的是,我们当然会对针对这个类的测试单元进行修改,以适应变化。但是如果对这个类仅有调用关系的类的行为定义没有变化则相应的单元测试仍然是可靠和充分的,同时如果包含行为变化的类的对象的状态定义与其没有直接的关系,测试单元仍然起效。这种结果也是封装原则的优势体现。

 

 回页首

 

 

 回页首

 

关于作者

 

 申文波: 1973年出生,现于艾昂科技上海公司任资深技术顾问。在关系数据库对象建模方面有较长的工作经验,熟悉Java语言,目前从事的工作领域主要包括OOA、OOD和企业应用。您可以通过邮件 alair_china@yahoo.com.cn与他联系。

 

 

 回页首

 

对本文的评价

 

太差! (1) 需提高 (2) 一般;尚可 (3) 好文章 (4) 真棒!(5)

建议?

 

 

 

介绍一个还不错的英文测试咨询公司的网站http://www.clarkwa

  初步看了他的一篇文章《JUnit Primer》(http://www.learndiary.com/disDiaryContentAction.do?searchDiaryID=1359&goalID=1359&naviStr=a10a60a01328),还不错,在他的网站上还有一些公开的文章,可以看。

  作者(公司拥有者)介绍:

About the Author

Mike Clark is a consultant, author, speaker, and most important, he's an experienced programmer. He is the developer of JUnitPerf, an open source collection of JUnit extensions for continuous performance testing. He also wrote the JUnit FAQ and serves as its editor for the JUnit community. Mike frequently writes and speaks on test-driven development using JUnit. He helps teams build better software faster through his company, Clarkware Consulting, Inc.

(转帖)JUnit最佳实践

转自:http://www.javaresearch.org/article/showarticle.jsp?column=526&thread=9139

JUnit最佳实践

cherami 转贴  (参与分:712253,专家分:22624)   发表:2003-09-16 19:57   版本:1.0   阅读:6075次 

 

Martin Fowler说过:“当你试图打印输出一些信息或调试一个表达式时,写一些测试代码来替代那些传统的方法。”一开始,你会发现你总是要创建一些新的Fixture,而且测试似乎使你的编程速度慢了下来。然而不久之后,你会发现你重复使用相同的Fixture,而且新的测试通常只涉及添加一个新的测试方法。

你可能会写许多测试代码,但你很快就会发现你设想出的测试只有一小部分是真正有用的。你所需要的测试是那些会失败的测试,即那些你认为不会失败的测试,或你认为应该失败却成功的测试。

我们前面提到过测试是一个不会中断的过程。一旦你有了一个测试,你就要一直确保其正常工作,以检验你所加入的新的工作代码。不要每隔几天或最后才运行测试,每天你都应该运行一下测试代码。这种投资很小,但可以确保你得到可以信赖的工作代码。你的返工率降低了,你会有更多的时间编写工作代码。

不要认为压力大,就不写测试代码。相反编写测试代码会使你的压力逐渐减轻,应为通过编写测试代码,你对类的行为有了确切的认识。你会更快地编写出有效率地工作代码。下面是一些具体的编写测试代码的技巧或较好的实践方法:

1. 不要用TestCase的构造函数初始化Fixture,而要用setUp()和tearDown()方法。

2. 不要依赖或假定测试运行的顺序,因为JUnit利用Vector保存测试方法。所以不同的平台会按不同的顺序从Vector中取出测试方法。

3. 避免编写有副作用的TestCase。例如:如果随后的测试依赖于某些特定的交易数据,就不要提交交易数据。简单的会滚就可以了。

4. 当继承一个测试类时,记得调用父类的setUp()和tearDown()方法。

5. 将测试代码和工作代码放在一起,一边同步编译和更新。(使用Ant中有支持junit的task.)

6. 测试类和测试方法应该有一致的命名方案。如在工作类名前加上test从而形成测试类名。

7. 确保测试与时间无关,不要依赖使用过期的数据进行测试。导致在随后的维护过程中很难重现测试。

8. 如果你编写的软件面向国际市场,编写测试时要考虑国际化的因素。不要仅用母语的Locale进行测试。

9. 尽可能地利用JUnit提供地assert/fail方法以及异常处理的方法,可以使代码更为简洁。

10.测试要尽可能地小,执行速度快。

版权声明  本篇文章对您是否有帮助?  投票: 是    否     投票结果:     8       2

 

 

 

作者其它文章:

使用Checklist提升项目的质量

提升项目组的开发效率

使用JkUnMount

Apache2.0运行模型分析及性能调整

优化Apache 2.0 性能

作者全部文章     查看作者的Blog 

 

 

 上一篇文章   返回〔软件测试〕 

 下一篇文章  

 

 

 

 

文字广告链接

 

 

       快速业务开发平台+在线自定义WEB报表平台+多级数据上报解决方案

       上海网域网:上海、香港、美国服务器租用 服务器托管专家

       数巨报表: 全程图形化设计无须代码,适合J2EE、ASP及.NET等环境,功能强大的Web报表工具

       MatriX BI: 让企业迅速获得挖掘数据潜力,释放IT系统价值的能力

 

 

关于 JR  |  版权声明  |  联系我们 

 

©2002-2005 JR 版权所有 沪ICP备05019622号  

 

 

 

StrutsTestCase入门文章(转)

这是这个开源项目的主页,完整的介绍它的运用,可以作为入门教材使用。

转自:http://strutstestcase.sourceforge.net/

StrutsTestCase for JUnit v2.1.3

Now supporting Struts 1.2, including Tiles and Sub-Applications!

Questions? Comments? Check out the user forums.

 

What is it?

StrutsTestCase for JUnit is an extension of the standard JUnit TestCase class that provides facilities for testing code based on the Struts framework. StrutsTestCase provides both a Mock Object approach and a Cactus approach to actually run the Struts ActionServlet, allowing you to test your Struts code with or without a running servlet engine. Because StrutsTestCase uses the ActionServlet controller to test your code, you can test not only the implementation of your Action objects, but also your mappings, form beans, and forwards declarations. And because StrutsTestCase already provides validation methods, it's quick and easy to write unit test cases.

StrutsTestCase is compliant with the Java Servlet 2.2, 2.3, and 2.4 specifications, and supports Struts 1.2, and Cactus 1.6.1 and JUnit 3.8.1.

Please note that StrutsTestCase is no longer backwards compatible with Struts 1.0. The last release compatible with Struts 1.0 is StrutsTestCase v2.0.

Where does it live?

StrutsTestCase for JUnit is hosted at SourceForge. You can find the latest and greatest release of StrutsTestCase here.

Javadocs for all of the StrutsTestCase classes can be found here, and there is also a discussion forum and a list of frequently asked questions.

Mock Testing vs. In-Container Testing

There are two popular approaches to testing server-side classes: mock objects, which test classes by simulating the server container, and in-container testing, which tests classes running in the actual server container. StrutsTestCase for JUnit allows you to use either approach, with very minimal impact on your actual unit test code. In fact, because the StrutsTestCase setup and validation methods are exactly the same for both approaches, choosing one approach over the other simply effects which base class you use!

StrutsTestCase for JUnit provides two base classes, both of which are extensions of the standard JUnit TestCase. MockStrutsTestCase uses a set of HttpServlet mock objects to simulate the container environment without requiring a running servlet engine. CactusStrutsTestCase uses the Cactus testing framework to test Struts classes in the actual server container, allowing for a testing environment more in line with the actual deployment environment.

Please note that while the following examples use the MockStrutsTestCase approach, you could choose to use the Cactus approach by simply subclassing from CactusStrutsTestCase without changing another line of code!

How does it work?

Please note that the StrutsTestCase distribution comes with these and other examples, as well as step-by-step instructions for running them -- see the README file in the examples directory for more details. As well, there are many more methods in the StrutsTestCase library than are illustrated here -- check out the javadocs for a complete picture of what you can do with StrutsTestCase!

Using the popular cookbook approach, let's consider the following code snippet:

public class LoginAction extends Action {

    public ActionForward perform(ActionMapping mapping,

                                 ActionForm form,

                                 HttpServletRequest request,

                                 HttpServletResponse response)

    {

        String username = ((LoginForm) form).getUsername();

        String password = ((LoginForm) form).getPassword();

        ActionErrors errors = new ActionErrors();

        if ((!username.equals("deryl")) || (!password.equals("radar")))

            errors.add("password",new ActionError("error.password.mismatch"));

        if (!errors.empty()) {

            saveErrors(request,errors);

            return mapping.findForward("login");

        }

        // store authentication info on the session

        HttpSession session = request.getSession();

        session.setAttribute("authentication", username);

        // Forward control to the specified success URI

        return mapping.findForward("success");

}

So, what are we doing here? Well, we receive an ActionForm bean which should contain login information. First, we try to get the username and password information, and then check to see if it is valid. If there is a mismatch in the username or password values, we then create an ActionError message with a key to a message catalogue somewhere, and then try to forward to the login screen so we can log in again. If the username and password match, however, we store some authentication information in the session, and we try to forward to the next page.

There are several things we can test here:

Does the LoginForm bean work properly? If we place the appropriate parameters in the request, does this bean get instantiated correctly?

If the username or password doesn't match, do the appropriate errors get saved for display on the next screen? Are we sent back to the login page?

If we supply the correct login information, do we get to the correct page? Are we sure there are no errors reported? Does the proper authentication information get saved in the session?

StrutsTestCase gives you the ability to test all of these conditions within the familiar JUnit framework. All of the Struts setup -- which really amounts to starting up the ActionServlet -- is taken care of for you.

So, how do we actually do it? Let's start by creating an empty test case, which we extend from the base StrutsTestCase class:

public class TestLoginAction extends MockStrutsTestCase {

    public void setUp() { super.setUp(); }

    public void tearDown() { super.tearDown(); }

    public TestLoginAction(String testName) { super(testName); }

    public void testSuccessfulLogin() {}

}

NOTE: If you choose to override the setUp() method, you must explicitly call super.setUp(). This method performs some important initialization routines, and StrutsTestCase will not work if it is not called.

The first thing we need to do is to tell Struts which mapping to use in this test. To do so, we specify a path that is associated with a Struts mapping; this is the same mechanism that the Struts tag library method uses.

public class TestLoginAction extends MockStrutsTestCase {

    public TestLoginAction(String testName) { super(testName); }

    public void testSuccessfulLogin() {

       setRequestPathInfo("/login");

    }

}

NOTE: By default, the Struts ActionServlet will look for the file WEB-INF/struts-config.xml, so you must place the directory that contains WEB-INF in your CLASSPATH. If you would like to use an alternate configuration file, please see the setConfigFile() method for details on how this file is located.

Next we need to pass form bean properties, which we send via the request object (again, just as Struts does):

public class TestLoginAction extends MockStrutsTestCase {

    public TestLoginAction(String testName) { super(testName); }

    public void testSuccessfulLogin() {

       setRequestPathInfo("/login");

       addRequestParameter("username","deryl");

       addRequestParameter("password","radar");

    }

}

Finally, we need to get the Action to do its thing, which just involves executing the actionPerform method:

public class TestLoginAction extends MockStrutsTestCase {

    public TestLoginAction(String testName) { super(testName); }

    public void testSuccessfulLogin() {

       setRequestPathInfo("/login");

       addRequestParameter("username","deryl");

       addRequestParameter("password","radar");

       actionPerform();

    }

}

That's all you have to do to get the ActionServlet to process your request, and if all goes well, then nothing will happen. But we're not done yet -- we still need to verify that everything happened as we expected it to. First, we want to make sure we got to the right page:

public class TestLoginAction extends MockStrutsTestCase {

    public TestLoginAction(String testName) { super(testName); }

    public void testSuccessfulLogin() {

       setRequestPathInfo("/login");

       addRequestParameter("username","deryl");

       addRequestParameter("password","radar");

       actionPerform();

       verifyForward("success");

    }

}

It's worth noting here that when you verify which page you ended up at, you can use the Struts forward mapping. You don't have to hard code filenames -- the StrutsTestCase framework takes care of this for you. Thus, if you were to change where "success" pointed to, your tests would still work correctly. All in the spirit of Struts.

Next, we want to make sure that authentication information was stored properly:

public class TestLoginAction extends MockStrutsTestCase {

    public TestLoginAction(String testName) { super(testName); }

    public void testSuccessfulLogin() {

       setRequestPathInfo("/login");

       addRequestParameter("username","deryl");

       addRequestParameter("password","radar");

       actionPerform();

       verifyForward("success");

       assertEquals("deryl",(String) getSession().getAttribute("authentication"));

    }

}

Here we're getting the session object from the request, and checking to see if it has the proper attribute and value. You could just as easily place an object on the session that your Action object expects to find. All of the servlet classes available in the StrutsTestCase base classes are fully functioning objects.

Finally, we want to make sure that no ActionError messages were sent along. We can use a built in method to make sure of this condition:

public class TestLoginAction extends MockStrutsTestCase {

    public TestLoginAction(String testName) { super(testName); }

    public void testSuccessfulLogin() {

       setRequestPathInfo("/login");

       addRequestParameter("username","deryl");

       addRequestParameter("password","radar");

       actionPerform();

       verifyForward("success");

       assertEquals("deryl",(String) getSession().getAttribute("authentication"));

       verifyNoActionErrors();

    }

}

So, now that we've written one test case, it's easy to write another. For example, we'd probably want to test the case where a user supplies incorrect login information. We'd write such a test case like the following:

public void testFailedLogin() {

    addRequestParameter("username","deryl");

    addRequestParameter("password","express");

    setRequestPathInfo("/login");

    actionPerform();

    verifyForward("login");

    verifyActionErrors(new String[] {"error.password.mismatch"});

    assertNull((String) getSession().getAttribute("authentication"));

}

Now, this looks quite similar to our first test case, except that we're passing incorrect information. Also, we're checking to make sure we used a different forward, namely one that takes us back to the login page, and that the authentication information is not on the session.

We're also verifying that the correct error messages were sent. Note that we used the symbolic name, not the actual text. Because the verifyActionErrors() method takes a String array, we can verify more than one error message, and StrutsTestCase will make sure there is an exact match. If the test produced more error messages than we were expecting, it will fail; if it produced fewer, it will also fail. Only an exact match in name and number will pass.

It's that easy! As you can see, StrutsTestCase not only tests the implementation of your Action objects, but also the mappings that execute them, the ActionForm beans that are passed as arguments, and the error messages and forward statements that result from execution. It's the whole enchilada!

Testing Tiles in Struts 1.2

The Tiles framework, which is integrated into Struts 1.2, is a flexible templating mechanism designed to easily re-use common user experience elements. StrutsTestCase now provides support for testing applications that use Tiles, allowing any test case to verify that an Action object uses the correct Tiles definition. Tiles testing is similar to calling verifyActionForward, with a twist:

public class TestLoginAction extends MockStrutsTestCase {

    public TestLoginAction(String testName) { super(testName); }

    public void testSuccessfulLogin() {

       setConfigFile("/WEB-INF/struts-config.xml");

       setRequestPathInfo("/login.do");

       addRequestParameter("username","deryl");

       addRequestParameter("password","radar");

       actionPerform();

       verifyTilesForward("success","success.tiles.def");

    }

}

This is similar to our previous test cases, except that we're additionally passing in a definition name to verify that this Action uses a given Tiles definition when resolving the expected forward. If it uses a different Tiles definition, or if the expected definition does not exist, then this test will fail. Additionally, you can call verifyInputTilesForward to verify that an Action uses an input mapping, and that input mapping is the expected Tiles definition:

public class TestLoginAction extends MockStrutsTestCase {

    public TestLoginAction(String testName) { super(testName); }

    public void testSuccessfulLogin() {

       setConfigFile("/WEB-INF/struts-config.xml");

       setRequestPathInfo("/login.do");

       addRequestParameter("username","deryl");

       addRequestParameter("password","radar");

       actionPerform();

       verifyInputTilesForward("success.tiles.def");

    }

}

Testing Sub-Applications in Struts 1.2

Struts 1.2 introduces the concept of sub-applications, or modules -- a powerful mechanism for dividing an application into functional components. StrutsTestCase now provides support for testing sub-applications, which extends the concepts discussed in the previous examples. The general idea is still the same: you can point a unit test to a configuration file, execute an action, and validate the results. The methods for setting the configuration file and executing an action are a little different, however, as the following example shows:

public class TestLoginAction extends MockStrutsTestCase {

    public TestLoginAction(String testName) { super(testName); }

    public void testSuccessfulLogin() {

       setConfigFile("mymodule","/WEB-INF/struts-config-mymodule.xml");

       setRequestPathInfo("/mymodule","/login.do");

       addRequestParameter("username","deryl");

       addRequestParameter("password","radar");

       actionPerform();

       verifyForward("success");

       assertEquals("deryl",(String) getSession().getAttribute("authentication"));

       verifyNoActionErrors();

    }

}

As you can see, this looks very similar to our other test cases. The first important difference is in setting the configuration file. Here, we set the files and associate this configuration file with a given sub-application name. This allows the StrutsTestsCase library to pass this configuration information so that the ActionServlet can correctly identify the sub-application. Note that the same rules apply to setting the CLASSPATH to locate the configuration file as those mentioned above.

The other important difference is in setting the request path information. Here, we set not only the path information, but also the sub-application names. The combination of these two parameters is equivalent to the entire request path; in our test case above, this would be equivalent to a path like this: /mymodule/login.do. It is important to note that when using this method, the first argument must contain only the sub-application name, and the second argument must contain the rest of the path not including the sub-application name. Otherwise, the request path will be incorrectly constructed, resulting in a spurious test failure.

Copyright (C) 2004 Deryl Seale

JUnit入门文章(转)(包括英文原文及中文译文)

英文原文转自:http://www.clarkware.com/articles/JUnitPrimer.html

中文译文转自:http://140.109.17.94/ArecaChen/Test/JUnit_Primer.htm

××××××××××××××××××××××××××××××××××××××××××××××××××××××××

英文原文:

JUnit Primer

Summary

This article demonstrates a quick and easy way to write and run JUnit test cases and test suites. We'll start by reviewing the key benefits of using JUnit and then write some example tests to demonstrate its simplicity and effectiveness.

The two-day, on-site Test-Driven Development with JUnit Workshop is an excellent way to learn JUnit and test-driven development through lecture and a series of hands-on exercises guided by Mike Clark.

Table of Contents

This article contains the following sections:

Introduction

Why Use JUnit?

Design of JUnit

Step 1: Install JUnit

Step 2: Write a Test Case

Step 3: Write a Test Suite

Step 4: Run the Tests

Step 5: Organize the Tests

Testing Idioms

Training and Mentoring

Resources

Why Use JUnit?

Before we begin, it's worth asking why we should use JUnit at all. The subject of unit testing always conjures up visions of long nights slaving over a hot keyboard trying to meet the project's test case quota. However, unlike the Draconian style of conventional unit testing, using JUnit actually helps you write code faster while increasing code quality. Once you start using JUnit you'll begin to notice a powerful synergy emerging between coding and testing, ultimately leading to a development style of only writing new code when a test is failing.

Here are just a few reasons to use JUnit:

JUnit tests allow you to write code faster while increasing quality.

Yeah, I know, it sounds counter-intuitive, but it's true! When you write tests using JUnit, you'll spend less time debugging, and you'll have confidence that changes to your code actually work. This confidence allows you to get more aggressive about refactoring code and adding new features.

Without tests, it's easy to become paranoid about refactoring or adding new features because you don't know what might break as a result. With a comprehensive test suite, you can quickly run the tests after changing the code and gain confidence that your changes didn't break anything. If a bug is detected while running tests, the source code is fresh in your mind, so the bug is easily found. Tests written in JUnit help you write code at an extreme pace and spot defects quickly.

JUnit is elegantly simple.

Writing tests should be simple - that's the point! If writing tests is too complex or takes too much time, there's no incentive to start writing tests in the first place. With JUnit, you can quickly write tests that exercise your code and incrementally add tests as the software grows.

Once you've written some tests, you want to run them quickly and frequently without disrupting the creative design and development process. With JUnit, running tests is as easy and fast as running a compiler on your code. In fact, you should run your tests every time you run the compiler. The compiler tests the syntax of the code and the tests validate the integrity of the code.

JUnit tests check their own results and provide immediate feedback.

Testing is no fun if you have to manually compare the expected and actual result of tests, and it slows you down. JUnit tests can be run automatically and they check their own results. When you run tests, you get simple and immediate visual feedback as to whether the tests passed or failed. There's no need to manually comb through a report of test results.

JUnit tests can be composed into a hierarchy of test suites.

JUnit tests can be organized into test suites containing test cases and even other test suites. The composite behavior of JUnit tests allows you to assemble collections of tests and automatically regression test the entire test suite in one fell swoop. You can also run the tests for any layer within the test suite hierarchy.

Writing JUnit tests is inexpensive.

Using the JUnit testing framework, you can write tests cheaply and enjoy the convenience offered by the testing framework. Writing a test is as simple as writing a method that exercises the code to be tested and defining the expected result. The framework provides the context for running the test automatically and as part of a collection of other tests. This small investment in testing will continue to pay you back in time and quality.

JUnit tests increase the stability of software.

The fewer tests you write, the less stable your code becomes. Tests validate the stability of the software and instill confidence that changes haven't caused a ripple-effect through the software. The tests form the glue of the structural integrity of the software.

JUnit tests are developer tests.

JUnit tests are highly localized tests written to improve a developer's productivity and code quality. Unlike functional tests, which treat the system as a black box and ensure that the software works as a whole, unit tests are written to test the fundamental building blocks of the system from the inside out.

Developer's write and own the JUnit tests. When a development iteration is complete, the tests are promoted as part and parcel of the delivered product as a way of communicating, "Here's my deliverable and the tests which validate it."

JUnit tests are written in Java.

Testing Java software using Java tests forms a seamless bond between the test and the code under test. The tests become an extension to the overall software and code can be refactored from the tests into the software under test. The Java compiler helps the testing process by performing static syntax checking of the unit tests and ensuring that the software interface contracts are being obeyed.

JUnit is free!

Design of JUnit

JUnit is designed around two key design patterns: the Command pattern and the Composite pattern.

A TestCase is a command object. Any class that contains test methods should subclass the TestCase class. A TestCase can define any number of public testXXX() methods. When you want to check the expected and actual test results, you invoke a variation of the assert() method.

TestCase subclasses that contain multiple testXXX() methods can use the setUp() and tearDown() methods to initialize and release any common objects under test, referred to as the test fixture. Each test runs in the context of its own fixture, calling setUp() before and tearDown() after each test method to ensure there can be no side effects among test runs.

TestCase instances can be composed into TestSuite hierarchies that automatically invoke all the testXXX() methods defined in each TestCase instance. A TestSuite is a composite of other tests, either TestCase instances or other TestSuite instances. The composite behavior exhibited by the TestSuite allows you to assemble test suites of test suites of tests, to an arbitrary depth, and run all the tests automatically and uniformly to yield a single pass or fail status.

Step 1: Install JUnit

First, download the latest version of JUnit, referred to below as junit.zip.

Then install JUnit on your platform of choice:

Windows

To install JUnit on Windows, follow these steps:

Unzip the junit.zip distribution file to a directory referred to as %JUNIT_HOME%.

Add JUnit to the classpath:

set CLASSPATH=%JUNIT_HOME%\junit.jar

Unix (bash)

To install JUnit on Unix, follow these steps:

Unzip the junit.zip distribution file to a directory referred to as $JUNIT_HOME.

Add JUnit to the classpath:

export CLASSPATH=$JUNIT_HOME/junit.jar

Test the installation by using either the textual or graphical test runner to run the sample tests distributed with JUnit.

Note: The sample tests are not contained in the junit.jar, but in the installation directory directly. Therefore, make sure that the JUnit installation directory is in the CLASSPATH.

To use the textual test runner, type:

java junit.textui.TestRunner junit.samples.AllTests

To use the graphical test runner, type:

java junit.swingui.TestRunner junit.samples.AllTests

All the tests should pass with an "OK" (textual runner) or a green bar (graphical runner). If the tests don't pass, verify that junit.jar is in the CLASSPATH.

Step 2: Write a Test Case

First, we'll write a test case to exercise a single software component. We'll focus on writing tests that exercise the component behavior that has the highest potential for breakage, thereby maximizing our return on testing investment.

To write a test case, follow these steps:

Define a subclass of TestCase.

Override the setUp() method to initialize object(s) under test.

Optionally override the tearDown() method to release object(s) under test.

Define one or more public testXXX() methods that exercise the object(s) under test and assert expected results.

The following is an example test case:

Example JUnit Test Case

import junit.framework.TestCase;

public class ShoppingCartTest extends TestCase {

    private ShoppingCart cart;

    private Product book1;

    /**

     * Sets up the test fixture.

     *

     * Called before every test case method.

     */

    protected void setUp() {

        cart = new ShoppingCart();

        book1 = new Product("Pragmatic Unit Testing", 29.95);

        cart.addItem(book1);

    }

    /**

     * Tears down the test fixture.

     *

     * Called after every test case method.

     */

    protected void tearDown() {

        // release objects under test here, if necessary

    }

    /**

     * Tests emptying the cart.

     */

    public void testEmpty() {

        cart.empty();

   

        assertEquals(0, cart.getItemCount());

    }

    /**

     * Tests adding an item to the cart.

     */

    public void testAddItem() {

        Product book2 = new Product("Pragmatic Project Automation", 29.95);

        cart.addItem(book2);

        double expectedBalance = book1.getPrice() + book2.getPrice();

 

        assertEquals(expectedBalance, cart.getBalance(), 0.0);

        assertEquals(2, cart.getItemCount());

    }

    /**

     * Tests removing an item from the cart.

     *

     * @throws ProductNotFoundException If the product was not in the cart.

     */

    public void testRemoveItem() throws ProductNotFoundException {

        cart.removeItem(book1);

        assertEquals(0, cart.getItemCount());

    }

    /**

     * Tests removing an unknown item from the cart.

     *

     * This test is successful if the

     * ProductNotFoundException is raised.

     */

    public void testRemoveItemNotInCart() {

        try {

            Product book3 = new Product("Pragmatic Version Control", 29.95);

            cart.removeItem(book3);

            fail("Should raise a ProductNotFoundException");

        } catch(ProductNotFoundException expected) {

            // successful test

        }

    }

}

(The complete source code for this example is available in the Resources section).

Step 3: Write a Test Suite

Next, we'll write a test suite that includes several test cases. The test suite will allow us to run all of its test cases in one fell swoop.

To write a test suite, follow these steps:

Write a Java class that defines a static suite() factory method that creates a TestSuite containing all the tests.

Optionally define a main() method that runs the TestSuite in batch mode.

The following is an example test suite:

Example JUnit Test Suite

import junit.framework.Test;

import junit.framework.TestSuite;

public class EcommerceTestSuite {

    public static Test suite() {

        TestSuite suite = new TestSuite();

        //

        // The ShoppingCartTest we created above.

        //

        suite.addTestSuite(ShoppingCartTest.class);

        //

        // Another example test suite of tests.

        //

        suite.addTest(CreditCardTestSuite.suite());

        //

        // Add more tests here

        //

        return suite;

    }

    /**

     * Runs the test suite using the textual runner.

     */

    public static void main(String[] args) {

        junit.textui.TestRunner.run(suite());

    }

}

Step 4: Run the Tests

Now that we've written a test suite containing a collection of test cases and other test suites, we can run either the test suite or any of its test cases individually. Running a TestSuite will automatically run all of its subordinate TestCase instances and TestSuite instances. Running a TestCase will automatically invoke all of its public testXXX() methods.

JUnit provides both a textual and a graphical user interface. Both user interfaces indicate how many tests were run, any errors or failures, and a simple completion status. The simplicity of the user interfaces is the key to running tests quickly. You should be able to run your tests and know the test status with a glance, much like you do with a compiler.

To run our test case using the textual user interface, use:

java junit.textui.TestRunner ShoppingCartTest

The textual user interface displays "OK" if all the tests passed and failure messages if any of the tests failed.

To run the test case using the graphical user interface, use:

java junit.swingui.TestRunner ShoppingCartTest

The graphical user interface displays a Swing window with a green progress bar if all the tests passed or a red progress bar if any of the tests failed.

The EcommerceTestSuite can be run similarly:

java junit.swingui.TestRunner EcommerceTestSuite

Step 5: Organize the Tests

The last step is to decide where the tests will live within our development environment.

Here's the recommended way to organize tests:

Create test cases in the same package as the code under test. For example, the com.mydotcom.ecommerce package would contain all the application-level classes as well as the test cases for those components.

To avoid combining application and testing code in your source directories, create a mirrored directory structure aligned with the package structure that contains the test code.

For each Java package in your application, define a TestSuite class that contains all the tests for validating the code in the package.

Define similar TestSuite classes that create higher-level and lower-level test suites in the other packages (and sub-packages) of the application.

Make sure your build process includes the compilation of all tests. This helps to ensure that your tests are always up-to-date with the latest code and keeps the tests fresh.

By creating a TestSuite in each Java package, at various levels of packaging, you can run a TestSuite at any level of abstraction. For example, you can define a com.mydotcom.AllTests that runs all the tests in the system and a com.mydotcom.ecommerce.EcommerceTestSuite that runs only those tests validating the e-commerce components.

The testing hierarchy can extend to an arbitrary depth. Depending on the level of abstraction you're developing at in the system, you can run an appropriate test. Just pick a layer in the system and test it!

Here's an example test hierarchy:

Example JUnit Test Hierarchy

AllTests (Top-level Test Suite)

    SmokeTestSuite (Structural Integrity Tests)

        EcommerceTestSuite

            ShoppingCartTestCase

            CreditCardTestSuite

                AuthorizationTestCase

                CaptureTestCase

                VoidTestCase

            UtilityTestSuite

                MoneyTestCase

        DatabaseTestSuite

            ConnectionTestCase

            TransactionTestCase

    LoadTestSuite (Performance and Scalability Tests)

        DatabaseTestSuite

            ConnectionPoolTestCase

        ThreadPoolTestCase

Testing Idioms

Keep the following things in mind when writing JUnit tests:

The software does well those things that the tests check.

Test a little, code a little, test a little, code a little...

Make sure all tests always run at 100%.

Run all the tests in the system at least once per day (or night).

Write tests for the areas of code with the highest probability of breakage.

Write tests that have the highest possible return on your testing investment.

If you find yourself debugging using System.out.println(), write a test to automatically check the result instead.

When a bug is reported, write a test to expose the bug.

The next time someone asks you for help debugging, help them write a test.

Write unit tests before writing the code and only write new code when a test is failing.

Training and Mentoring

Reduce defects and improve design and code quality with a two-day, on-site Test-Driven Development with JUnit Workshop that quickly spreads the testing bug throughout your team.

I also offer JUnit mentoring to help your keep the testing momentum.

Contact me for more information.

Resources

Source Code - Complete source code for the ShoppingCartTest example

JUnit - The official JUnit website

JUnit FAQ - Frequently asked questions and answers

A Dozen Ways to Get the Testing Bug by Mike Clark (java.net, 2004)

Pragmatic Unit Testing by Andy Hunt and Dave Thomas (The Pragmatic Programmers, 2003)

JUnitPerf - JUnit test decorators for continuous performance testing

JDepend - A Java package dependency analyzer with example JUnit test cases

About the Author

Mike Clark is a consultant, author, speaker, and most important, he's an experienced programmer. He is the developer of JUnitPerf, an open source collection of JUnit extensions for continuous performance testing. He also wrote the JUnit FAQ and serves as its editor for the JUnit community. Mike frequently writes and speaks on test-driven development using JUnit. He helps teams build better software faster through his company, Clarkware Consulting, Inc.

Copyright © 1999-2005 Clarkware Consulting, Inc.

All Rights Reserved

×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××

中文译文:

JUnit入門

 

 摘要

本文展示如何使用JUnit測試框架撰寫及執行簡單的測試案例及測試系列。 

 

原作者:Mike Clark Clarkware Consulting, Inc. October 7, 2000

翻譯:Areca Chen 2002/1/23

 

 

  簡介

本文的目的是展示一個撰寫及執行JUnit測試案例及測試系列(suites)(或測試包)簡單快速的方法。我們一開始先探討使用JUnit的主要優點然後撰寫一些測試範例以展示其簡單性及效力。

本文包含下列各節:

簡介

為什麼使用JUnit?

JUnit的設計

第一步:撰寫一個測試案例

第二步:撰寫一個測試系列

第三步:執行測試

第四步:組織測試

測試慣例

訓練及顧問指導

資源

 

在你開始之前,請確認你已下載並安裝下列的軟體:

JUnit (3.1 or later)

 

 為什麼使用JUnit?

在我們開始之前,我們要問為什麼我們要使用JUnit?單元測試的主題呈現在我們腦中的往往是長夜的思考試著去符合專案測試案例的配額。不管如何,不像傳統單元測試的殘酷特性,使用JUnit實際上在你提升程式碼的品質時JUnit測試仍允許你更快速的撰寫程式。只要你開始使用JUnit你將開始注意到介於程式碼及測試之間有一個強大的結合力,最終你的開發風格是只有當一個失敗的測試時才寫新的程式碼(only writing new code when a test is failing)。

以下列出使用JUnit的理由:

 

在你提升程式碼的品質時JUnit測試仍允許你更快速的撰寫程式

是的,我知道,那聽起來似乎不是很直覺,但那是事實。當你使用JUnit撰寫測試,你將花更少的時間除蟲,同時對你程式碼的改變更 俱有信心。這個信心讓你更積極重整程式碼並增加新的功能。沒有測試,對於重整及增加新功能你會變得沒有信心;因為你不知道有甚麼東西會破壞產出的結果。採用一個綜合的測試系列,你可以在改變程式碼之後快速的執行多個測試並對於你的變動並未破壞任何東西感到有信心。在執行測試時如果發現臭蟲,原始碼仍然清楚的在你腦中,因此很容易找到臭蟲。在JUnit中撰寫的測試幫助你以一種極 大(extreme)的步伐撰寫程式及快速的找出缺點。

JUnit非常簡單

撰寫測試應該很簡單--這是重點!如果撰寫測試太複雜或太耗時間,便無法要求程式設計師撰寫測試。使用JUnit你可以快速的撰寫測試並檢測你的程式碼並逐 步隨著程式碼的成長增加測試。只要你寫了一些測試,你想要快速並頻繁的執行測試而不至於中斷建立設計及開發程序。使用JUnit執行測試就像編譯你的程式碼那麼容易。事實上,你應該執行編譯時也執行測試。編譯是 檢測程式碼的語法而測試是檢查程式碼的完整性(integrity)。

JUnit測試檢驗其結果並提供立即的回饋。

如果你是以人工比對測試的期望與實際結果那麼測試是很不好玩的,而且讓你的速度慢下來。JUnit測試可以自動執行並且檢查他們自己的結果。當你執行測試,你獲得簡單且立即的回饋; 比如測試是通過或失敗。而不再需要人工檢查測試結果的報告。

JUnit測試可以合成一個測試系列的層級架構。

JUnit可以把測試組織成測試系列;這個測試系列可以包含其他的測試或測試系列。JUnit測試的合成行為允許你組合多個測試並自動的回歸(regression)從頭到尾測試整個測試系列。你也可以執行測試系列層級架構中任何一層的測試。

撰寫JUnit測試所費不多。

使用Junit測試框架,你可以很便宜的撰寫測試並享受由測試框架所提供的信心。撰寫一個測試就像寫一個方法一樣簡單;測試是檢驗要測試的程式碼並定義期望的結果。這個測試框架提供自動執行測 試的背景;這個背景並成為其他測試集合的一部份。在測試少量的投資將持續讓你從時間及品質中獲得回收。

JUnit測試提升軟體的穩定性。

你寫的測試愈少;你的程式碼變的愈不穩定。測試使得軟體穩定並逐步累積信心;因為任何變動不會造成漣漪效應而漫及整個軟體。測試可以形成軟體的完整結構的膠結。

JUnit測試是開發者測試。

JUnit測試是高度區域性(localized)測試;用以改善開發者的生產力及程式碼品質。不像功能測試(function test)視系統為一個黑箱以確認軟體整體的工作性為主,單元測試是由內而外測試系統基礎的建構區塊。開發者撰寫並擁有JUnit測試。每當一個開發反覆(iteration)完成,這個測試便包裹成為交付軟體的一部份 提供一種溝通的方式,「這是我交付的軟體並且是通過測試的。」

JUnit測試是以Java寫成的。

使用Java測試Java軟體形成一個介於測試及程式碼間的無縫(seamless)邊界。在測試的控制下測試變成整個軟體的擴充同時程式碼可以被重整。Java編譯器的單元測試靜態語法檢查可已幫助測試程序並且確認遵守軟體介面的約定。

JUnit是免費的(JUnit is free.)

 

 JUnit的設計

JUnit是以兩個關鍵設計樣式來設計的:指令樣式(Command pattern)及合成樣式(Composite pattern)。

TestCase是一個指令物件。任何包含測試方法的類別都是TestCase的子類別。TestCase可以定義任意數量的testXXX()方法。當你要檢查期望與實際的測試結果,你啟動assert()方法的各種類型(variaton)。

TestCase子類別包含多個testXXX()方法;可以使用setUp()及tesrDown()方法初始化及釋放測試下的任何一般的物件,這個子類別形同測試的基礎設備(fixture)。每一個測試在其本身基礎設備的背景下執行,在每一個測試方法之前呼叫setUp()及之後呼叫tearDown()以確保沒有副作用影響測試的執行。

TestCase實例物件可以合成為TestSuite層級架構;在這個TestSuite層級架構中可以自動啟動定義在TestCase實例物件中的所有testXXX()方法。一個TestSuite是其他多個測試的一個合成物件(composite),其中包括TestCase實例物件及其他的TestSuite實例物件。這個由TestSuite代表的合成物件行為允許你組合測試的測試系列的測試系列到任意深度,並且自動一致性(uniformly)的執行所有測試以產出個別的通過或失敗的狀態。

 

第一步:撰寫一個測試案例

首先,我們將撰寫一個測試案例以便檢測單一軟體元件。我們將聚焦於撰寫測試;這個測試檢驗這個元件的行為;而這個物件的行為是有著最高的破損的可能性,因此可以從測試的投資獲得最大的回收。

撰寫測試案例請依據下列的步驟:

定義一個TestCase的子類別。

覆寫setUp()方法以初始化測試中的一個或多個物件。

覆寫tearDown()方法以釋放測試中的一個或多個物件。

定義一個或多個公開的testXXX()方法;這些方法檢驗這些測試中的物件並且評估期望的結果。

定義一個靜態的suite()工廠方法;這個工廠方法構建一個TestSuite其中包含TestCase的所有testXXX()方法。

隨意的定義一個main()方法以批次的方式執行TestCase。

 

下列是一個測試案例的範例:

(這個範例完整的程式碼可以在資源一節中找的到。)

測試案例的範例(Example Test Case)

import junit.framework.Test;

import junit.framework.TestCase;

import junit.framework.TestSuite;

public class ShoppingCartTest extends TestCase {

    private ShoppingCart _bookCart;

    private Product _defaultBook;

    /**

     *以特定名稱建構一個ShoppingCartTest。

     *

     *建構函數是以測試案例的名稱當作參數

     */

    public ShoppingCartTest(String name) {

        super(name);

    }

    /**

     * 設定測試設備

     *在測試案例方法之前呼叫

     * /

    protected void setUp() {

        _bookCart = new ShoppingCart();

        _defaultBook = new Product("Extreme Programming", 23.95);

        _bookCart.addItem(_defaultBook);

    }

    /**

     *釋放測試設備

     *

     *在測試案例方法之後呼叫

     *

     */

    protected void tearDown() {

        _bookCart = null;

    }

    /**

     *測試在cart中增加一個產品

     *

     */

    public void testProductAdd() {

        Product newBook = new Product("Refactoring", 53.95);

        _bookCart.addItem(newBook);

        double expectedBalance = _defaultBook.getPrice() + newBook.getPrice();

 

        assertEquals(expectedBalance, _bookCart.getBalance(), 0.0);

        assertEquals(2, _bookCart.getItemCount());

    }

    /**

     *測試清空cart

     *

     */

    public void testEmpty() {

        _bookCart.empty();

   

        assertTrue(_bookCart.isEmpty());

    }

    /**

     *測試從cart中移除產品

     *

     *如果此產品不在cart中丟出一個ProductNotFoundException的例外

     *

     */

    public void testProductRemove() throws ProductNotFoundException {

        _bookCart.removeItem(_defaultBook);

        assertEquals(0, _bookCart.getItemCount());

        assertEquals(0.0, _bookCart.getBalance(), 0.0);

    }

    /**

     *測試從cart中移除一個未知的產品

     *

     *如果ProductNotFoundException例外產生表示測試成功

     *

     */

    public void testProductNotFound() {

        try {

            Product book = new Product("Ender's Game", 4.95);

            _bookCart.removeItem(book);

            fail("Should raise a ProductNotFoundException");

        } catch(ProductNotFoundException success) {

            // 測試成功

        }

    }

    /**

     *組合並傳回一個這個測試案例所有測試方法的測試系列

     *傳回一個非空值(non-null)的測試系列

     */

    public static Test suite() {

        //這裡使用的想法是加入所有的testXXX()方法到測試系列中。

        //

        TestSuite suite = new TestSuite(ShoppingCartTest.class);

        //下面是另一種作法,但增加愈多的測試案例方法愈有可能發生錯誤

        //

        // TestSuite suite = new TestSuite();

        // suite.addTest(new ShoppingCartTest("testEmpty"));

        // suite.addTest(new ShoppingCartTest("testProductAdd"));

        // suite.addTest(new ShoppingCartTest("testProductRemove"));

        // suite.addTest(new ShoppingCartTest("testProductNotFound"));

        //

        return suite;

    }

    /**

     *執行此測試案例(Runs the test case)

     */

    public static void main(String args[]) {

        junit.textui.TestRunner.run(suite());

    }

}

 

 

 

  第二步:撰寫一個測試系列

其次,我們將撰寫一個測試系列其中包含許多測試案例。此測試系列將允許我們從頭到尾執行其所有的測試案例。

撰寫測試系列請依循下列的步驟:

定義一個TestCase的子類別。

定義一個靜態的suite()工廠方法;這個方法構建一個TestSuite以包含所以的測試。

隨意定義一個main()方法以批次方式執行這個TestSuite。

下列是測試系列的範例:

測試系列範例(Example Test Suite)

import junit.framework.Test;

import junit.framework.TestCase;

import junit.framework.TestSuite;

public class EcommerceTestSuite extends TestCase {

    /**

     * 以特定名稱建構一個EcommerceTestSuite

     *

     *建構函數是以測試案例的名稱當作參數

     */

    public EcommerceTestSuite(String name) {

        super(name);

    }

    /**

     *組合並傳回一個測試系列包含所有已知的測試。

     *新的測試應該在此加入

     *傳回一個非空值(non-null)的測試系列

     */

    public static Test suite() {

        TestSuite suite = new TestSuite();

        //我們在前面構建的ShoppingCartTest

        //

        suite.addTest(ShoppingCartTest.suite());

        //另一個測試系列的範例,在測試系列中加入其他的測試系列

        //

        suite.addTest(CreditCardTestSuite.suite());

        return suite;

    }

    /**

     *執行此測試系列

     */

    public static void main(String args[]) {

        junit.textui.TestRunner.run(suite());

    }

}

 

 

 

第三步:執行測試

現在我們撰寫了一個測試系列其中包含一堆測試案例及其他的測試系列,我們可以執行這個測試系列或者其中任何個別的測試案例。執行TestSuite將自動執行所有的TestCase及TestSuite實例物件。執行一個TestCase將自動啟動其所有公開的testXXX()方法。

JUnit提供文字及圖形使用者界面。兩種使用者介面都可以指出多少個測試被執行、任何錯誤或失敗、及一個簡單的完成狀態。簡化使用者介面是快速執行測試的關鍵。你應該簡單瞭解就能夠執行你的測試並知道測試的狀態,就像你在編譯上所做的一樣。

文字使用者介面(junit.textui.TestRunner)如果通過所有測試則顯示『OK』而如果失敗則顯示失敗訊息。

圖形使用者界面(junit.swingui.TestRunner)顯示浮動視窗;如果所有測試皆通過則其中有一個進度桿顯示為綠色,否則進度桿顯示為紅色。

一般而言,TestSuite及TestCase類別應定義一個main()方法;main()利用適當的使用者介面。我們寫的測試到目前為止皆定義一個main()方法來使用文字使用者介面。

由main()定義的使用文字使用者介面執行我們的測試案例時,使用:

java ShoppingCartTest

另一種方式,使用文字使用者介面執行測試,使用:

java junit.textui.TestRunner ShoppingCartTest

或這使用圖形使用者介面時,使用:

java junit.swingui.TestRunner ShoppingCartTest

EcommerceTestSuite可以以類似的方法執行。

 

第四步:組織測試

最後一步是決定測試在我們的開發環境中應存在於何處。

這裡有一些建議如何組織我們的測試:

把測試案例建立在與我們要測試的程式碼相同的包裹(package)中。例如:com.mydotcom.ecommerce包裹包含所有的應用程式階 級的類別及這些元件的測試案例。

在你的原始碼資料夾中避免結合應用程式與測試程式碼,建立一個鏡射(mirrored)的資料夾結構對應於此包裹結構;並在鏡射資料夾中存放你的測試碼。

為你的應用程式中的Java包裹定義一個TestSuite類別;在這個TestSuite類別中包含所有測試這個包裹內之程式的測試。

定義類似的TestSuite類別;此TestSuite類別在此應用程式中的其他包裹(及子包裹)中構建高階及低階測試系列。

確認你的建構程序包含所有測試的編輯物(compilation)。這樣做有助於確認你的測試可以保持與最後的程式碼同步以維持測試是最新的。

經由在每一個Java包裹中建立一個TestSuite,在各種包裹層次中,你可以在每一個抽象層中執行一個TestSuite。例如,你可以定義一個com.mydotcom.AllTests執行系統中所有的測試,及定義一個com.mydotcom.ecommerce.EcommerceTestSuite只有執行電子交易元件的測試。

測試層級架構可以擴充到任意的深度。其深度與你開發系統的抽象層次有關,你可以執行一個相稱的測試。只要選擇你的系統層次並測試它即可。

下面的範例是測是的層級架構:

測試層級架構範例(Example Testing Hierarchy)

AllTests (Top-level Test Suite)

    SmokeTestSuite (Structural Integrity Tests)

        EcommerceTestSuite

            ShoppingCartTestCase

            CreditCardTestSuite

                AuthorizationTestCase

                CaptureTestCase

                VoidTestCase

            UtilityTestSuite

                MoneyTestCase

        DatabaseTestSuite

            ConnectionTestCase

            TransactionTestCase

    LoadTestSuite (Performance and Scalability Tests)

        DatabaseTestSuite

            ConnectionPoolTestCase

        ThreadPoolTestCase

 

 

 

測試慣例

當你測試時請謹記下面的事項:

軟體運作良好的事物都是經過測試檢驗的。

測試一點點,程式碼寫一點點,測試一點點,程式碼寫一點點......

請確認所有測試都能100%通過。

每天(夜)至少執行系統中所有的測試一次。

要測試的程式碼是最可能錯誤的區域。

撰寫最可能回收測試投資的測試。

如果你使用System.out.println()除蟲,寫一個測試自動檢查其結果。

如果發現臭蟲,寫一個測試揭露這個臭蟲。

如果下次有人要求你幫他除蟲,幫他寫一個測試。

撰寫程式碼之前先寫單元測試;而且只有當一個測試失敗才寫新的程式碼。

 

訓練及顧問指導

想要學習更多有關測試嗎?身為Clarkware Consulting的首席顧問,我提供線上JUnit訓練及顧問指導服務幫助你的團隊使用JUnit提升生產力及有效的應用JUnit測試你的專案。開發服務有可以幫你的專案成功。這些服務可以量身訂製以符合你特殊專案的需求。

請以email 或電話720.851.2014與我聯絡。

 

資源

Source Code -ShoppingCartTest範例完整的原始碼。

JUnit - 官方網站

JUnit Cookbook -手冊

JUnit FAQ

"JUnit Test Infected: Programmers Love Writing Tests" by Gamma, E. and Beck, K.

"JUnit A Cook's Tour" by Gamma, E. and Beck, K.

JUnitPerf -收集JUnit測試修飾者(decorators)用以衡量包含於現有JUnit測試的功能其執行效率及可達成性(scalability) (A collection of JUnit test decorators used to measure the performance and scalability of functionality contained within existing JUnit tests.)

JDepend -  一個Java包裹相關的分析器包含JUnit測試案例的範例。(A Java package dependency analyzer with example JUnit test cases)

Java Tools for Extreme Programming: Mastering Open Source Tools Including Ant, JUnit, and Cactus, by Richard Hightower, Nicholas Lesiecki (John Wiley & Sons, 2001)

Refactoring: Improving The Design Of Existing Code, by Fowler, M. (Addison-Wesley, 1999)

Extreme Programming Explained, by Beck, K. (Addison-Wesley, 2000)

--------------------------------------------------------------------------------

作者簡介(About the author)

Mike Clark是Clarkware Consulting, Inc.的首席顧問,提供客戶的軟體架構、設計、開發、及執行效率諮詢。Mike有十年左右使用各種技術開發及交付軟體的經驗及五年實際經驗的Java專家;在Java中的經驗強調使用J2EE技術於伺服器端的開發。 

 

--------------------------------------------------------------------------------

Copyright © 1999-2001 Clarkware Consulting, Inc.

All Rights Reserved

本文獲授權同意翻譯函:

Hi Areca,

You may translate the JUnit Primer as long as the original copyright

remains intact and the text is translated without further modification.

Enjoy!

Mike

=====

Mike Clark

Clarkware Consulting, Inc.

http://www.clarkware.com

720.851.2014

Dear  Mike,

I've read the articles " Junit Primer"on the web site and like it very much.

It will be great if I could translate it into traditional Chinese and publish on our own site http://www.dotspace.idv.tw) to help those who want to explore JUnit more. I will, of course, declare the source and "hyperlink" those to your original documents. I'll be very grateful if you could give your authorization for me to do this.

Our web site contents about software engineering topics like XP, UML, Rup, Design Patterns, Framework, those are all in traditional Chinese.

        Regards,

Areca Chen

2002/2/8

 

 

junit测试类的包结构应该与功能类的包结构一致

  为对应的功能类写junit测试类时,应该使用和功能类一样的包结构。这可以从junit3.8.1中的faq中得知:

在junit3.8.1的faq中(/doc/faq/faq.htm#organize_1)这样说:

  Organizing Tests:  top 

Where should I put my test files?

You can place your tests in the same package and directory as the classes under test.

For example:

src

   com

      xyz

         SomeClass.java

         SomeClassTest.java

Or, if you feel this clutters the source directory, you can place the tests in a separate parallel directory structure with package alignment.

For example:

src

   com

      xyz

         SomeClass.java

test

   com

      xyz

         SomeClassTest.java

These approaches allow the tests to access to all the package visible methods and fields of the classes under test.

  而且,在java中,为了保持一个类在整个世界上的唯一性,使用反转域名的方案。当然测试类也应该遵守这个方案。而不是造另外的一种方案。

  以上见解若有误,还望见者给以指正。谢谢。