JUnit了解

JUnit 入门

1.JUnit
软件测试在软件投入使用前,对软件需求分析,设计规格说和编码进行最后的审查,这是软件质量保证的关键步骤。大量的数据表明,在软件测试的工作量往往占软件开发总工作的40%以上,而且成本不菲。所以软件测试砸整个开发过程中具有举足轻重的地位。

软件测试在软件开发过程中跨越了两个阶段:通常在编写每一个模块之后就要做必要的测试,这就叫单元测试,编码和单元测试属于软件开发过程中的同一阶段。在这个阶段之后,需要对软件系统进行各种综合的测试,即综合测试,他属于软件工程的测试阶段。

单元测试简介
软件测试是软件开发的重要组成部分但是很多开发者去忽略了这一点。他们认为测试应该有专门的测试人员来做。因为他们对自己写出的代码很“了解”,但他们却忽视了重要的一点,如果成员不对自己的代码进行测试,他们怎么知道自己写的代码会按照预期的方式运行呢?

单元测试就是开发者写一段测试代码来验证自己编写的一段代码运行是否正确。一般来说,一个单元测试用来判定在给定条件写某个函数的行为。例如,如果项测试一个类型的某个函数返回的对象是否是原来预期的对象。

那么为什么要进行单元测试呢?当编写完一段代码之后,系统会进行变异,然后开始运行。如果编译都没有通过,运行就更不可能了。
如果编译通过只能说明没有语法错误,但却无法保证这段代码在任何时候,都会按照自己的预期结果运行。所有的这些问题单元测试都可以解决。编写单元测试可以验证自己编写的代码是否按照预期运行。总之,单元测试可以使开发者的工作变得越来越轻松。

1. JUnit简介
JUnit的特性
JUnit是一个开放源代码的Java测试框架,用在编写和运行可重复的测试上。他是单元测试框架体系xUnit的一个实例,包括如下特性。
(1).用于测试期望结果的断言。
(2)用于共享共同测试数据的测试工具。
(3)用于方便地组织和运行测试的测试套件。
(4)星图形和文本的测试运行器。

JUnit的优点
(1) 可以使测试代码与产品代码分开。这个有利于代码的打包发布和测试代码的管理。
(2) 针对某一个类的测试代码,通过较少的改动使可以应用另一个类的测试。JUnit提供了一个编写测试类的框架,使测试代码的编写更加方便。
(3) 易于集成程序中的构建过程中。JUnit和Ant的结合开可以实施增量开发。
(4) JUnit的源代码是公开的,故而可以进行二次开发。
(5) JUnit具有很强的扩展性,可以方便地对JUnit进行扩展。

JUnit框架
JUnit一共7个包,其核心的包是junit.framework 和 junit.runner. framework包负责整个测试对象的构建,runner负责测试驱动。JUnit有四个重要的类,他们分别是TestSuite,TestCase,TestResult和TestRunner。另外,JUnit还包括Test接口和TestListenter类,前几个类属于junit.framework包,后一个类在不同的环境下是不同的。下面来简单介绍一些这几个类。

TestResult
TestResult 负责收集TestCase所执行的结果。它将结果分类,分为客户可预测的错误和没有预测的错误。它还将测试结果转发到TestListener处理。

TestRunner
TestRunner 是客户对象调用的起点,它负责对整个测试过程进行跟踪。它能够显示测试结果,并且报告测试的进度。

TestListenter
TestListenter 包含4个方法:addError()  addFailuer()  startTest() 和 endTest()。它是对测试结果的处理和对测试驱动过程的工作特征进行提取。

Test接口
Test 接口用来测试和收集测试结果。Test接口采用了Composite设计模式,它是单独的测试用例, 聚合的测试模式以及测试扩展的共同接口。它的countTestCase方法用来统计本次测试有多少个TestCase。在另一个方法run()中,参数TestResult作为测试结果的事例,run方法用于执行本次测试。

TestCase抽象类
TestCase抽象类用来定义测试中的固定方法。TestCase是Test接口的抽象实现,由于TestCase是一个抽象类,因此不能被实例化,只能被继承。其构造函数可以根据输入的测试名称来创建一个测试用例,提供测试名的目的在于方便测试失败是查找失败的测试用例。编写TestCase的子类用于测试时,需要注意以下事项。
(1) 一次测试纸测试一个对象,这样容易定位出错的位置。对于一个TestCase,只测试一个对象,一个测试方法只测试一个对象中的方法。
(2) 最好在assert函数中给出失败的原因,这样有助于查错。
(3) 在setUp和tearDown中的代码不应该与测试有关的,而应是全局相关的。

TestSuite 测试套件类
TestSuite 类负责组装多个TestCase。测试中可能包括了对测试类的多个测试,而每个测试可能就是一个测试用例,TestSuite负责收集组合这些测试,以便可以在一个测试中完成全部测试。

JUnit介绍及其快速使用

JUnit 是 Java 社区中知名度最高的单元测试工具。它诞生于 1997 年,由 Erich Gamma 和 Kent Beck 共同开发完成。其中 Erich Gamma 是经典著作《设计模式:可复用面向对象软件的基础》一书的作者之一,并在 Eclipse 中有很大的贡献;Kent Beck 则是一位极限编程(XP)方面的专家和先驱。

  麻雀虽小,五脏俱全。JUnit 设计的非常小巧,但是功能却非常强大。Martin Fowler 如此评价 JUnit:在软件开发领域,从来就没有如此少的代码起到了如此重要的作用。它大大简化了开发人员执行单元测试的难度,特别是 JUnit 4 使用 Java 5 中的注解(annotation)使测试变得更加简单。

  从推出到现在,JUnit3.8.1和JUnit4的工作原理和使用区别还是比较大的,下面首先用一段代码来演示JUnit3.8.1的快速使用,以便熟悉JUnit的原理
 
    1.首先,我们在Eclipse的项目中创建一个待测试的类Hello.java,代码如下:

public class Hello {

    public int abs(int num)
    {
        return num>0?num:-num;
    }
    public double division(int a,int b)
    {
        return a/b;
    }
}

  2.右击该类,选择 新建->JUnit测试用例,选择JUnit3.8.1,setUp和tearDown方法,点击下一步,选择需要测试的方法,JUnit会自动生成测试的代码框架,手动添加自己的测试代码后如下: 

import junit.framework.TestCase;

public class HelloTest extends TestCase {

    private Hello hello;
    public HelloTest()
    {
        super();
        System.out.println("a new test instance...");
    }
    //测试前JUnit会调用setUp()建立和初始化测试环境
    protected void setUp() throws Exception {
        super.setUp();                                    //注意:在Junit3.8.1中这里要调用父类的setUp()
        hello=new Hello();
        System.out.println("call before test...");
    }
    //测试完成后JUnit会调用tearDown()清理资源,如释放打开的文件,关闭数据库连接等等
    protected void tearDown() throws Exception {
        super.tearDown();                                //注意:在Junit3.8.1中这里要调用父类的tearDown()
        System.out.println("call after test... ");
    }

    //测试Hello类中的abs函数
    public void testAbs() {
        System.out.println("test the method abs()");
        assertEquals(16, hello.abs(16));
        assertEquals(11, hello.abs(-10));//在这里,会出现故障,应该把左边的参数改为10
        assertEquals(0, hello.abs(0));
       
    }

    //测试Hello类中的division函数
    public void testDivision() {
        System.out.println("test the method division()");
        assertEquals(3D, hello.division(6, 2));
        assertEquals(6D, hello.division(6, 1));
        assertEquals(0D, hello.division(6, 0));//在这里,会出现错误,java.lang.ArithmeticException: /by zero
       
    }

}

  3.运行该测试类,输出如下:

a new test instance...
a new test instance...
call before test...
test the method abs()
call after test...

call before test...
test the method division()
call after test...
  从上面的输出结果中,可以看出JUnit大概会生成如下的测试代码:

try {
    HelloTest test = new HelloTest();     // 建立测试类实例
    test.setUp();             // 初始化测试环境
    test.testAbs();             // 测试abs方法
    test.tearDown();             // 清理资源
}
catch(Exception e){}

try {
    HelloTest test = new HelloTest();     // 建立测试类实例
    test.setUp();             // 初始化测试环境
    test.testDivision();         // 测试division方法
    test.tearDown();             // 清理资源
}
catch(Exception e){}

  所以,每测试一个方法,JUnit就会创建一个xxxTest实例,如上面就分别生成了两个HelloTest实例来分别测试abs和division方法。

 

Junit的学习笔记

1.      Junit中几个不得不掌握的方法

a)        assertNull
b)        assertNotNull
c)        assertEquals
d)        assertNotEquals
e)        assertTrue
f)         assertFalse
g)        assertSame
h)        assertNotSame
i)          fail
 
2.      使用方法
a)        类名规则 : 类名 +Test 比如你有一个需要测试的类是 HelloMyPlog 则你的测试类就叫做 HelloMyPlogTest
b)        方法规则: test+ 方法名字 testShowPlogInfo()
c)        必须继承TestCase类
d)        setUp与tearDown,这两个函数是Junit中提供初始化和反初始化每个测试方法的。setUp在每个测试方法调用前被调用,负责初始化测试方法所需要的测试环境;tearDown在每个测试方法被调用之后被调用,负责撤销测试环境。
 
区分failexception
-          fail ,期望出现的错误。产生原因: assert 函数出错(如 assertFalse(true) ); fail 函数产生(如 fail(……) )。
-          exception ,不期望出现的错误,属于 unit test 程序运行时抛出的异常。它和普通代码运行过程中抛出的 runtime 异常属于一种类型。
对于 assert
 
关于TestSuit
您怎样才能一次运行多个测试?
只要您有了两个测试,您可能就希望一起运行他们。您当然可以每次只运行一个,但是很快您就会感到厌倦。JUnit提供了一个对象,TestSuite,以方便您一次完成任意多的测试一起运行。

例如:只运行一个测试用例,您可能会执行:

TestResult result  =  ( new  MoneyTest( " testMoneyMoneyBag " )).run();

运行两个测试用例,可以先产生一个套件(Suite),然后将这两个测试用例包含其中,如下:

TestSuite suite  =   new  TestSuite();

suite.addTest(
new  MoneyTest( " testMoneyMoneyBag " ));
suite.addTest(
new  MoneyTest( " testSimpleAdd " ));
TestResult result 
=  suite.run();

      组装TestSuite,运行更多的test。在Junit中,TestTestCaseTestSuite三者组成了composiste pattern。通过组装自己的TestSuite,可以完成对添加到这个TestSuite中的所有的TestCase的调用。而且这些定义的TestSuite还可以组装成更大的TestSuite,这样同时也方便了对于不断增加的TestCase的管理和维护。

       它的另一个好处就是,可以从这个 TestCase 树的任意一个节点( TestSuite TestCase )开始调用,来完成这个节点以下的所有 TestCase 的调用。提高了 unit test 的灵活性。
使用例子(其实跟上面是一样的)
import  junit.framework.Test; 
import  junit.framework.TestSuite; 
public   class  TestAll ...
public class TestAll...
    
//定义一个suite,对于junit的作用可以视为类似于java应用程序的main。
    public static Test suite()...
        TestSuite suite 
= new TestSuite("Running all tests."); 
        suite.addTestSuite( TestCase1.
class); 
        suite.addTestSuite( TestCase2.
class); 
        
return suite; 
    }
 
}
 
 
使用Ant junit task。我们除了使用java来直接运行junit之外,我们还可以使用junit提供的junit task与ant结合来运行。涉及的几个主要的ant task如下:
-          <junit>,定义一个junit task
-          <batchtest>,位于<junit>中,运行多个TestCase
-          <test>,位于<junit>中,运行单个TestCase
-          <formatter>,位于<junit>中,定义一个测试结果输出格式
-          <junitreport>,定义一个junitreport task
-          <report>,位于<junitreport>中,输出一个junit report
具体的语法请参见相关文档。
10.   使用例子:
< junit  printsummary ="yes"  haltonfailure ="no" >  
    
< classpath >  
        
< path  refid ="classpath" />  
        
< pathelement  location ="${dist.junit}" />  
    
</ classpath >  
    
< formatter  type ="brief"  usefile ="false" />  
    
< formatter  type ="xml" />  
    
< batchtest  todir ="${doc.junitReport}" >  
        
< fileset  dir ="${dist.junit}"  includes ="**/*Test.class"   />  
    
</ batchtest >  
</ junit >  
< junitreport  todir ="${doc.junitReport}" >  
    
< fileset  dir ="${doc.junitReport}" >  
        
< include  name ="TEST*-*.xml" />  
    
</ fileset >  
    
< report  format ="frames"  styledir ="${junit.styleDir}"  todir ="${doc.junitReport}" />  
</ junitreport >  
检查表
       junit的使用并不很难,然而要书写一个好的TestCase却并非易事。一个不好的TestCase往往是既浪费了时间,也起不了实际的作用。相反,一个好的TestCase,不仅可以很好的指出代码中存在的问题,而且也可以作为代码更准确的文档,同时还在持续集成的过程中起非常重要的作用。在此给出书写TestCase时需要注意的几点:
-          测试的独立性:一次只测试一个对象,方便定位出错的位置。这有2层意思:一个TestCase,只测试一个对象;一个TestMethod,只测试这个对象中的一个方法。
-          给测试方法一个合适的名字。
-          在assert函数中给出失败的原因,如:assertTrue( “… should be true”, ……),方便查错。在这个例子中,如果无法通过assertTrue,那么给出的消息将被显示。在junit中每个assert函数都有第一个参数是出错时显示消息的函数原型。
-          测试所有可能引起失败的地方,如:一个类中频繁改动的函数。对于那些仅仅只含有getter/setter的类,如果是由IDE(如Eclipse)产生的,则可不测;如果是人工写,那么最好测试一下。
-          在setUp和tearDown中的代码不应该是与测试方法相关的,而应该是全局相关的。如针对与测试方法A和B,在setUp和tearDown中的代码应该是A和B都需要的代码。
-          测试代码的组织:相同的包,不同的目录。这样,测试代码可以访问被测试类的protected变量/方法,方便测试代码的编写。放在不同的目录,则方便了测试代码的管理以及代码的打包和发布。一个例子如下:
src    <=源代码根目录
 |---com 
     |---mod1 
         |---class1 
junit   
< =测试代码根目录 
 |---com 
     
|---mod1 
         |---class1 
 
下面是Junit常用方法参数的一些说明。

Method Summary

static void

assertArrayEquals(byte[] expecteds, byte[] actuals)
          TODO: fix javadoc Asserts that two object arrays are equal.

static void

assertArrayEquals(char[] expecteds, char[] actuals)
          TODO: fix javadoc Asserts that two object arrays are equal.

static void

assertArrayEquals(int[] expecteds, int[] actuals)
          TODO: fix javadoc Asserts that two object arrays are equal.

static void

assertArrayEquals(long[] expecteds, long[] actuals)
          TODO: fix javadoc Asserts that two object arrays are equal.

static void

assertArrayEquals(java.lang.Object[] expecteds, java.lang.Object[] actuals)
          Asserts that two object arrays are equal.

static void

assertArrayEquals(short[] expecteds, short[] actuals)
          TODO: fix javadoc Asserts that two object arrays are equal.

static void

assertArrayEquals(java.lang.String message, byte[] expecteds, byte[] actuals)
          TODO: fix javadoc Asserts that two object arrays are equal.

static void

assertArrayEquals(java.lang.String message, char[] expecteds, char[] actuals)
          TODO: fix javadoc Asserts that two object arrays are equal.

static void

assertArrayEquals(java.lang.String message, int[] expecteds, int[] actuals)
          TODO: fix javadoc Asserts that two object arrays are equal.

static void

assertArrayEquals(java.lang.String message, long[] expecteds, long[] actuals)
          TODO: fix javadoc Asserts that two object arrays are equal.

static void

assertArrayEquals(java.lang.String message, java.lang.Object[] expecteds, java.lang.Object[] actuals)
          Asserts that two object arrays are equal.

static void

assertArrayEquals(java.lang.String message, short[] expecteds, short[] actuals)
          TODO: fix javadoc Asserts that two object arrays are equal.

static void

assertEquals(double expected, double actual, double delta)
          Asserts that two doubles or floats are equal to within a positive delta.

static void

assertEquals(java.lang.Object[] expecteds, java.lang.Object[] actuals)
          Deprecated. use assertArrayEquals

static void

assertEquals(java.lang.Object expected, java.lang.Object actual)
          Asserts that two objects are equal.

static void

assertEquals(java.lang.String message, double expected, double actual, double delta)
          Asserts that two doubles or floats are equal to within a positive delta.

static void

assertEquals(java.lang.String message, java.lang.Object[] expecteds, java.lang.Object[] actuals)
          Deprecated. use assertArrayEquals

static void

assertEquals(java.lang.String message, java.lang.Object expected, java.lang.Object actual)
          Asserts that two objects are equal.

static void

assertFalse(boolean condition)
          Asserts that a condition is false.

static void

assertFalse(java.lang.String message, boolean condition)
          Asserts that a condition is false.

static void

assertNotNull(java.lang.Object object)
          Asserts that an object isn't null.

static void

assertNotNull(java.lang.String message, java.lang.Object object)
          Asserts that an object isn't null.

static void

assertNotSame(java.lang.Object unexpected, java.lang.Object actual)
          Asserts that two objects do not refer to the same object.

static void

assertNotSame(java.lang.String message, java.lang.Object unexpected, java.lang.Object actual)
          Asserts that two objects do not refer to the same object.

static void

assertNull(java.lang.Object object)
          Asserts that an object is null.

static void

assertNull(java.lang.String message, java.lang.Object object)
          Asserts that an object is null.

static void

assertSame(java.lang.Object expected, java.lang.Object actual)
          Asserts that two objects refer to the same object.

static void

assertSame(java.lang.String message, java.lang.Object expected, java.lang.Object actual)
          Asserts that two objects refer to the same object.

static void

assertTrue(boolean condition)
          Asserts that a condition is true.

static void

assertTrue(java.lang.String message, boolean condition)
          Asserts that a condition is true.

static void

fail()
          Fails a test with no message.

static void

fail(java.lang.String message)
          Fails a test with the given message.

 

循序渐进学习Junit

使用最流行的开放资源测试框架之一学习单元测试基础。

使用JUnit可以大量减少Java代码中程序错误的个数,JUnit是一种流行的单元测试框架,用于在发布代码之前对其进行单元测试。现在让我们来详细研究如何使用诸如JUnit、Ant和Oracle9i JDeveloper等工具来编写和运行单元测试。

为什么使用JUnit?

多 数开发人员都同意在发布代码之前应当对其进行测试,并利用工具进行回归(regression)测试。做这项工作的一个简单方法是在所有Java类中以 main()方法实施测试。例如,假设使用ISO格式(这意味着有一个以这一格式作为参数的构造器和返回一个格式化的ISO字符串的toString() 方法)以及一个GMT时区来编写一个Date的子类。清单1 就是这个类的一个简单实现。

不过,这种测试方法并不需要单元测试限定语(qualifier),原因如下:

 

  • 在一个类中进行测试的最小单元是方法,你应当对每个方法进行单独测试,以准确地找出哪些方法工作正常,哪些方法工作不正常。
  • 即使前面的测试失败,也应当对各个方法进行测试。在此实施中,如果单个测试失败,后面的测试将根本不会运行。这就意味着你不会知道不良代码在你的实施中所占的百分比。
  • 测试代码会出现在生成的类中。这在类的大小方面可能不是什么问题,但却可能会成为安全性因素之一:例如,如果你的测试嵌入了数据库连接密码,那么这一信息将很容易用于已发布的类中。
  • 没有框架可以自动启动这一测试,你必须编写一个脚本来启动每一个测试。
  • 在编写一个报告时,你必须编写自己的实现,并定义规则,以方便地报告错误。

 

JUnit框架就是设计用来解决这些问题的。这一框架主要是所有测试实例(称为"TestCase")的一个父类,并提供工具来运行所编写的测试、生成报告及定义测试包(test suite)。

让我们为IsoDate类编写一个测试:这个IsoDateTest类类似于:

 

import java.text.ParseException;
import junit.framework.TestCase;


/**
 * Test case for <code>IsoDate</code>.
 */
public class IsoDateTest extends TestCase {
    
  public void testIsoDate() throws 
    Exception {
      IsoDate epoch=new IsoDate(
       "1970-01-01 00:00:00 GMT");
      assertEquals(0,epoch.getTime());

      IsoDate eon=new IsoDate(
       "2001-09-09 01:46:40 GMT");
      assertEquals(
        1000000000L*1000,eon.getTime());
    }
    
  public void testToString() throws   
    ParseException {
      IsoDate epoch=new IsoDate(0);
      assertEquals("1970-01-01 
        00:00:00 GMT",epoch.toString());

      IsoDate eon=new IsoDate(
        1000000000L*1000);
      assertEquals("2001-09-09 
        01:46:40 GMT",eon.toString());
  }
}

本例中要注意的重点是已经编写了一个用于测试的独立类, 因此可以对这些文件进行过滤,以避免将这一代码嵌入到将要发布的文档中。另外,本例还为你希望在你的代码中测试的每个方法编写了一个专用测试方法,因此你 将确切地知道需要对哪些方法进行测试、哪些方法工作正常以及哪些方法工作不正常。如果在编写实施文档之前已经编写了该测试,你就可以利用它来衡量工作的进 展情况。

安装并运行JUnit

要运行此示例测试实例,必须首先下载并安装JUnit。JUnit的最新版本可以在JUnit的网站 www.junit.org免 费下载。该软件包很小(约400KB),但其中包括了源代码和文档。要安装此程序,应首先对该软件包进行解压缩(junitxxx.zip)。它将创建一 个目录(junitxxx),在此目录下有文档(在doc目录中)、框架的应用编程接口(API)文档(在javadoc目录中)、运行程序的库文件 (junit.jar)以及示例测试实例(在junit目录中)。截至我撰写本文时,JUnit的最新版本为3.8.1,我是在此版本上对示例进行测试 的。

IsoDate Test

 

图1 运行IsoDate测试

 

要运行此测试实例,将源文件(IsoDate.javaIsoDateTest.java)拷贝到Junit的安装目录下,打开终端,进入该目录,然后输入以下命令行(如果你正在使用UNIX):

 

export CLASSPATH=.:./junit.jar
javac *.java
或者,如果你正在Windows,输入以下命令行

set CLASSPATH=.;junit.jar
javac *.java

 

这些命令行对CLASSPATH进行设置,使其包含当前目录中的类和junit.jar库,并编译Java源文件。

要在终端上运行该测试,输入以下命令行:

 

java junit.textui.TestRunner IsoDateTest

 

此命令行将运行该测试,并在图 1所示的控制台上显示测试结果。

才在此工具可以运行类名被传递到命令行中的单个测试。注意:只有对命令行的最后测试才在考虑之内,以前的测试都被忽略了。(看起来像一个程序错误,是吧?)

JUnit还提供了利用AWT(抽象窗口工具包)或Swing运行测试的图形界面。为了利用此图形界面运行测试,在终端上输入以下命令行:

 

java junit.awtui.TestRunner IsoDateTest

 

或者使用Swing界面:

 

java junit.swingui.TestRunner IsoDateTest

 

此命令行将显示图 2所示的界面。要选择一个测试并使其运行,点击带有三个点的按钮。这将显示CLASSPATH(还有测试包,但我们将在后面讨论)中所有测试的列表。要运行测试,点击"Run"按钮。测试应当正确运行,并在图 2所示的界面中显示结果。

在此界面中你应当选中复选框"Reload Classes Every Run",以便运行器在运行测试类之前对它们进行重新加载。这样就可以方便地编辑、编译并运行测试,而不需要每次都启动图形界面。

在该复选框下面是一个进度条,在运行较大的测试包时,该进度条非常有用。运行的测试、错误和失败的数量都会在进度条下面显示出来。再下面是一个失败列表和一个测试层次结构。失败消息显示在底部。通过点击Test Hierarchy(测试层次结构)面板,然后再点击窗口右上角的"Run"按钮,即可运行单个测试方法。请记住,使用命令行工具是不可能做到这些的。

注意,当运行工具来启动测试类时,这些类必须存在于CLASSPATH中。但是如果测试类存储在jar文件中,那么即使这些jar文件存在于CLASSPATH中,JUnit也不能找到这些测试类。

Swing interface

 

图2 用于运行测试的Swing界面

 

 

这并不是一种启动测试的方便方法,但幸运的是,JUnit已经被集成到了其他工具(如Ant和Oracle9i JDeveloper)中,以帮助你开发测试并使测试能够自动运行。

编写Junit测试实例

你已经看到了测试类的源代码对IsoDate实施进行了询问。现在让我们来研究这样的测试文件的实施。

测 试实例由junit.frameword.TestCase继承而来是为了利用JUnit框架的优点。这个类的名字就是在被测试类的名字上附加 "Test"。因为你正在测试一个名为IsoDate的类,所以其测试类的名字就是IsoDateTest。为了访问除私有方法之外的所有方法,这个类通 常与被测类在同一个包中。

注 意,你必须为你希望测试的在类中定义的每个方法都编写一个方法。你要测试构造器或使用了ISO日期格式的方法,因此你将需要为以ISO格式的字符串作为参 数的构造器和toString()方法编写一个测试方法。其命名方式与测试类的命名方式类似:在被测试方法(或构造器)前面附加"test"。

测 试方法的主体通过验证assertion(断言)对被测方法进行询问。例如,在toString()实施的测试方法中,你希望确认该方法已经对时间的设定 进行了很好的说明(对于UNIX系统来说,最初问世的时间为1970年1月1日的午夜)。要实施assertion,你可以使用Junit框架提供的 assertion方法。这些方法在该框架的junit.framework.Assert类中被实施,并且可以在你的测试中被访问,这是因为 Assert是TestCase的父类。这些方法可与Java中的关键字assert(是在J2EE 1.4中新出现的)相比。一些assertion方法可以检查原始类型(如布尔型、整型等)之间或对象之间是否相等(利用equals()方法检查两个对 象是否相等)。其他assertion方法检查两个对象是否相同、一个对象是否为"空"或"非空",以及一个布尔值(通常由一个表达式生成)是"真"还是 "假"。在表 1中对这些方法进行了总结。

对 于那些采用浮点类型或双精度类型参数的assertion,存在一个第三种方法,即采用一个delta值作为参数进行比较。另外还要注意, assertEquals()和assertSame()方法一般不会产生相同的结果。(两个具有相同值的字符串可以不相同,因为它们是两个具有不同内存 地址的不同对象。)因此,assertEquals()将会验证assertion的有效性,而assertSame()则不会。注意,对于表 1 中的每个assertion方法,你还有一种选择,就是引入另一个参数,如果assertion失败,该参数就会给出一条解释性消息。例如,assertEquals(int 期望值, int 实际值)就可以与一个诸如assertEquals(字符串消息,int期望值,int实际值)的消息一起使用。

当 一个assertion失败时,该assertion方法会抛出一个AssertFailedError或ComparisonFailure。 AssertionFailedError由java.lang.Error继承而来,因此你不必在测试方法的throws语句中对其进行声明。而 ComparisonFailure由AssertionFailedError继承而来,因此你也不必对其进行声明。因为当一个assertion失败 时会在测试方法中抛出一个错误,所以后面的assertion将不会继续运行。框架捕捉到这些错误并认定该测试已经失败后,就会打印出一条说明错误的消 息。这个消息由assertion生成,并且被传递到assertion方法(如果有的话)。

现在将下面一行语句添加到testIsoDate()方法的末尾:

 

assertEquals("This is a test",1,2);

现在编译并运行测试:

 

$ javac *.java
$ java junit.textui.TestRunner IsoDateTest
.F.
Time: 0,348
There was 1 failure:
1) testIsoDate(IsoDateTest)junit.framework
.AssertionFailedError: This is a test expected:<1> but was:<2>
      at IsoDateTest.testIsoDate
      (IsoDateTest.java:29)

FAILURES!!!
Tests run: 2,  Failures: 1,  Errors: 0

 

JUnit 为每个已处理的测试打印一个点,显示字母"F"来表示失败,并在assertion失败时显示一条消息。此消息由你发送到assertion方法的注释和 assertion的结果组成(自动生成)。从这里可以看出assertion方法的参数顺序对于生成的消息非常重要。第一个参数是期望值,而第二个参数 则是实际值。

如果在测试方法中出现了某种错误(例如,抛出了一个异常),该工具就会将其显示为一个错误(而不是由assertion失败而产生的一个"失败")。现在对IsoDateTest类进行修改,以将前面增加的一行语句用以下语句代替:

 

throw new Exception("This is a test"); 

然后编译并运行测试:

 

$ javac *.java
$ java junit.textui.TestRunner IsoDateTest 
.E.
Time: 0,284
There was 1 error:
1) testIsoDate(IsoDateTest)java.lang.
   Exception: This is a test at IsoDate
   Test.testIsoDate(IsoDateTest.java:30)

FAILURES!!!
Tests run: 2,  Failures: 0,  Errors: 1

 

该工具将该异常显示为一个错误。因此,一个错误表示一个错误的测试方法,而不是表示一个错误的测试实施。

Assert 类还包括一个fail()方法(该版本带有解释性消息),该方法将通过抛出AssertionFailedError来中断正在运行的测试。当你希望一个 测试失败而不会调用一个判定方法时,fail()方法是非常有用的。例如,如果一段代码应当抛出一个异常而未抛出,那么可以调用fail()方法使该测试 失败,方法如下:

 

public void testIndexOutOfBounds() {
  try {
       ArrayList list=new ArrayList();
       list.get(0);
       fail("IndexOutOfBoundsException   
           not thrown");
  } catch(IndexOutOfBoundsException e) {}
}

 

JUnit的高级特性

在 示例测试实例中,你已经同时运行了所有的测试。在现实中,你可能希望运行一个给定的测试方法来询问你正编写的实施方法,所以你需要定义一组要运行的测试。 这就是框架的junit.framework.TestSuite类的目的,这个类其实只是一个容器,你可以向其中添加一系列测试。如果你正在进行 toString()实施,并希望运行相应的测试方法,那么你可以通过重写测试的suite()方法来通知运行器,方法如下:

 

public static Test suite() {

  TestSuite suite= new TestSuite();
  suite.addTest(new IsoDateTest
("testToString"));
  return suite;
}

 

在此方法中,你用具体示例说明了一个TestSuite对象,并向其中添加了测试。为了在方法级定义测试,你可以利用构造器将方法名作为参数使测试类实例化。此构造器可按如下方法实施:

 

public IsoDateTest(String name) {
  super(name);
}

 

将上面的构造器和方法添加到IsoDateTest类(还需要引入junit.framework.Test和junit.framework.TestSuite),并在终端上输入:

selecting a test method

图3:选择一个测试方法

 

 

 
$ javac *.java
$ java junit.textui.TestRunner IsoDateTest
.
Time: 0,31
OK (1 test)

 

注意,在添加到测试包中的测试方法中,只运行了一个测试方法,即toString()方法。

你也可以利用图形界面,通过在图3所示的Test Hierarchy面板中选择测试方法来运行一个给定的测试方法。但是,要注意当整个测试包被运行一次后,该面板将被填满。

当你希望将一个测试实例中的所有测试方法添加到一个TestSuite对象中时,可以使用一个专用构造器,该构造器将此测试实例的类对象作为参数。例如,你可以使用IsoDateTest类实施suite()方法,方法如下:

 

public static Test suite() {
  return new TestSuite(IsoDateTest.class);
}

 

还有一些 情况,你可能希望运行一组由其他测试(如在工程发布之前的所有测试)组成的测试。在这种情况下,你必须编写一个实施suite()方法的类,以建立希望运 行的测试包。例如,假定你已经编写了测试类Atest和Btest。为了定义那些包含了类ATest中的所有测试和在BTest中定义的测试包的集合,可 以编写下面的类:

 

import junit.framework.*;

/**
 * TestSuite that runs all tests.
 */
public class AllTests {

  public static Test suite() {
     TestSuite suite= new TestSuite("All Tests");
     suite.addTestSuite(ATest.class);
     suite.addTest(BTest.suite());
     return suite;
  }
}

 

你完全可 以像运行单个测试实例那样运行这个测试包。注意,如果一个测试在一个套件中添加了两次,那么运行器将运行它两次(测试包和运行器都不会检查该测试是否是唯 一的)。为了了解实际的测试包的实施,应当研究Junit本身的测试包。这些类的源代码存在于JUnit安装的junit/test目录下。

test results

 

图4:显示测试结果的报告

 

将一个main()方法添加到一个测试或一个测试包中有时是非常方便的,因此可以在不使用运行器的情况下启动测试。例如,要将AllTests测试包作为一个标准的Java程序启动,可以将下面的main()方法添加到类中:

 

public static void main(String[] args) {
  junit.textui.TestRunner.run(suite());
}

 

现在可以通过输入java AllTests来启动这个测试包。

JUnit 框架还提供了一种有效利用代码的方法,即将资源集合到被称为fixture的对象集中。例如,该示例测试实例利用两个叫作epoch和eon的参考日期。 将这些日期重新编译到每个方法测试中只是浪费时间(而且还可能出现错误)。你可以用fixture重新编写测试,如清单2所示。

你 定义了两个参考日期,作为测试类的段,并将它们编译到一个setUp()方法中。这一方法在每个测试方法之前被调用。与其对应的方法是tearDown ()方法,它将在每个测试方法运行之后清除所有的资源(在这个实施中,该方法事实上什么也没做,因为垃圾收集器为我们完成了这项工作)。现在编译这个测试 实例(其源代码应当放在JUnit的安装目录中)并运行它:

 

$ javac *.java
$ java junit.textui.TestRunner IsoDateTest2
.setUp()
testIsoDate()
tearDown()
.setUp()
testToString()
tearDown()

Time: 0,373

OK (2 tests)

 

注意:在该测试实例中建立了参考日期,因此在任何测试方法中修改这些日期都不会对其他测试产生不利影响。你可以将代码放到这两个方法中,以建立和释放每个测试所需要的资源(如数据库连接)。

JUnit发布版还提供了扩展模式(在包junit.extensions中),即test decor-ators,以提供像重复运行一个给定的测试这样的新功能。它还提供了一个TestSuite,以方便你在独立的线程中同时运行所有测试,并在所有线程中的测试都完成时停止。

循序渐进学习JUnit:第二部分

作者:Michel Casabianca

利用Ant使测试自动化

如前面所述,测试运行器是非常原始的。如果你正在运行Ant来编译你的工程,那么编译文件是运行单元测试的好方法。(关于Ant的介绍,请参考我的文章《Ant简介》(Starting with Ant),发表于Oracle杂志2002年11/12月号中)。

假设你的源文件在src目录中,所生成的类在tmp目录中,并且junit.jar库位于工程的libdirectory目录中,那么你可以编译Java源文件,并使用清单3中所示的编译文件(在工程的根目录中)运行单元测试。

这个编译文件的核心是运行单元测试的测试目标。运行这些测试是这个目标junit的唯一任务。为了运行这一可选任务,必须首先将junit.jar库放到Ant安装目录下的lib目录中,然后下载并安装同一目录中的Ant可选任务库。清单3中 的示例嵌套了一个classpath类,它包括JUnit库和工程的类;示例中还嵌套了一个batchtest元素,它利用一个选择适当源文件的 fileset元素定义了将要运行的测试。这个任务还包括haltonfilure和haltonerror属性,它们告诉Ant在遇到一个失败或错误时 是否应当停止。如果将它们的值设置为"真",那么Ant在遇到第一个失败或错误时将会停止,编译将会失败(显然,这表示在运行测试过程中存在有问题)。另 一方面,如果将它们的值设置为"假",其结果就不是非常明确了(即使测试失败,编译也会成功),但所有测试仍将运行。printsummary属性指示 Ant是否显示运行测试的输出。数值withOutAndErr可以在开发测试时方便地告诉Ant显示标准输出和错误输出。数值off表示不显示任何内 容,而on只显示测试报告(没有测试类的输出)。junit任务具有很多属性,详细内容请参考Ant的文档。

为了测试这一编译文件,你需要建立名字为src、tmp和lib的目录。将junit.jar库放到lib目录中,并将前面看到的示例Java源文件放到src目录中。打开终端,进入该工程的根目录,并输入ant,其结果为:

 

$ ant
Buildfile: build.xml

clean:
   [delete] Deleting directory 
     /Users/casa/doc/oracle

     /junit/prj/tmp
   [mkdir] Created dir: /Users/casa
     /doc/oracle/junit/prj/tmp

bin:
    [javac] Compiling 4 source files 
      to /Users/casa/doc/oracle
      /junit/prj/tmp


test:
    [junit] Running IsoDateTest
    [junit] Tests run: 1, Failures: 
      0, Errors: 0, Time elapsed: 
           0,005 sec
    [junit] Running IsoDateTest2
    [junit] Tests run: 2, Failures: 0, 
      Errors: 0, Time elapsed: 0,031 sec
    [junit] Output:
    [junit] setUp()

    [junit] testIsoDate()
    [junit] tearDown()
    [junit] setUp()
    [junit] testToString()
    [junit] tearDown()

all:

BUILD SUCCESSFUL
Total time: 8 seconds

 

Ant还可以生成非常有用的HTML格式的测试报告。为了生成这样的报告,将前面的测试目标用以下目标代替:

 

<target name="test" depends="bin"
       description="Run JUnit tests">
  <junit haltonfailure="false"
        printsummary="withOutAndErr">
    <classpath refid="cp"/>
    <batchtest todir="${tmp}">
      <fileset dir="${src}" 
              includes="**/*Test*.java"/>
     </batchtest>

     <formatter type="xml"/>
  </junit>
  <junitreport todir="${tmp}">
    <fileset dir="${tmp}" 
            includes="TEST-*.xml"/>
    <report format="frames" 
           todir="${tmp}"/>
  </junitreport>
</target>

 

这 一目标与前面的目标相同,只是该目标在batchtext元素中增加了一个新的属性--todir,它告诉Ant在tmp目录中生成可扩展的标记语言 (XML)报告。该目标还增加了一个新的junitreport元素,以便由XML文件生成一个HTML报告。这一元素要求在安装Ant的lib目录中安 装Xalan库(详细内容见Ant文档的junitreport部分:ant.apache.org/manual/install.html)。这一元素还定义了使用todir属性生成的文件的目标目录。通过嵌套一个fileset元素来定义为生成这一报告而需要处理的XML文件。期望的输出格式利用嵌套的报告元素来实现。该对象将生成一个诸如图4所示的报告。

这 类报告在使单元测试自动运行时特别有用(比如在夜间编译期间)。在这些情况下,错误或失败不会中断测试,因此你必须将前面提到的junit任务的 haltonfailure和haltonerror属性设置为"假"。这一报告对于衡量实施进程也非常有用(比如当你必须重写已有代码时,或者在实施之 前已经编写了测试的情况下)。

Ant对启动JUnit图形运行器也非常有用。下面的对象将会启动Swing测试运行器:

 

<target name="testui" depends="bin"
        description="Run graphical JUnit">
<java classname="junit.swingui.TestRunner"
      classpathref="cp" 
      fork="true"/>
</target>

 

你应当在终端中运行这一对象,并且在另一个终端或你喜欢的IDE中使用Ant对其进行编译。这种方式使你不必在每次想要测试代码时都启动图形运行器。

在Oracle9i Jdeveloper中的JUnit集成

Oracle9i JDeveloper并没有基于网络集成JUnit,但是下载并安装这一插件只需要几分钟的时间。为了完成此过程,选择JDeveloper的"Help"菜单下的"Check for Updates"项。这样将会打开IDE更新向导,以连接到Oracle技术网站,下载该插件并安装它。当安装该插件后,需要关闭并重启Oracle9i JDeveloper。注意,向导还会下载相关的文档。

通过为每个任务提供向导,这个插件极大地提高了开发人员编写测试实例、测试包和fixture等的工作效率。要调用这些向导,点击"File"菜单下的"New"项,然后选择"General/Unit Tests"类,并从右侧的窗体中选择合适的向导。你也可以从界面上启动测试套件。

当 准备好对项目进行代码测试后,应当首先使用专用向导来编写fixture,然后测试实例向导可以利用它们集成到测试实例中。另外,还有一些用来生成自定义 测试fixture的向导以及生成商务组件和数据库连接测试fixture的向导。这后两种向导生成专用代码,以使用setUp()和tearDown ()方法设置和发布商务组件或数据库连接。

当 完成fixture后,下一步应当使用合适的向导来生成测试实例,这些向导可以让你选择要测试的类和方法。你还可以选择在这个测试中使用的 fixture。这将生成一个使用测试方法的主体完成的代码框架。最后应当生成套件来运行你的测试。这个专用向导让你选择要包括在套件中的测试,并为你生 成整个类。要启动一个测试套件,点击浏览器中的文件,并选择Run。这将会启动图形界面并运行套件的测试。

在"Help"菜单中选择"Help Topics",你将会在JDeveloper文档中找到关于如何使用这些向导的详细教程。这会打开帮助系统窗口。点击"Unit Testing with JUnit"项,然后选择合适的教程。

JUnit和JDeveloper之间的这种集成使你能够只编写单元测试中你感兴趣的那部分的代码,而让工具为你编写重复的代码。

下一步

访问JUnit网站
www.junit.org

下载
Oracle9i Jdeveloper
otn.oracle.com/software/products/jdev/

Oracle9i应用服务器
otn.oracle.com/software/products/ias/

学习Oracle9i JDeveloper扩展
otn.oracle.com/products/jdev/htdocs/partners/addins

阅读Oracle9i JDeveloper文档
otn.oracle.com/docs/products/jdev/

 

JUnit最佳实践

下面是一些在使用JUnit时应当注意的技巧:

 

  • 在实施之前编写测试代码。这是一种合同驱动的实施方式。
  • 只测试那些可能会中断的方法(也就是说,在多数情况下不应测试setter和getter方法)。要尽可能地多进行测试,以避免回归测试。当测试一个较大的应用程序时,你可以在夜间编译时运行所有测试。
  • 一定要使用正确的JUnit扩展来测试特殊的应用程序(如使用Castus测试J2EE应用程序)。

值得花费的时间

到 现在,你应当已经清楚地知道使用JUnit框架和合适的工具实施单元测试是多么快速而简单。关于单元测试的下一个目标是使你的CTO相信你在实施测试时所 必须花费的时间是为了以后节省更多的时间。但是,当你考虑在检查老代码、修正错误和发布一个调试过的版本上所花费的时间(它可能花费整个一天)时,在开发 过程的早期阶段捕获的代码错误毫无疑问是一项很好的投资。这里并没有考虑当错误代码不再位于块的顶部时开发人员必须遵循的"black magic"步骤,这些步骤包括:标记代码,制作一个分支、修正代码错误、进行发布,以及将代码修正合并到块中。所有这些步骤都非常耗时,并且容易产生错 误。

要开始使用单元测试和JUnit,请访问JUnit网站: www.junit.org。你将找到大量有用的文档(包括使用JUnit实施测试的详细说明书)、一个与JUnit集成的IDE列表,以及关于JUnit扩展工具的详细内容。

Michel Casabianca ( casa@sweetohm.net)是In-Fusio(一家为移动用户提供游戏服务的法国公司)的一名软件工程师,同时也是XML Pocket Reference(O'Reilly出版,2001年)一书的合著者。

表1:编写测试实例中所使用的判定方法

 

assertEquals(期望原型,实际原型)检查两个原型是否相等
assertEquals(期望对象,实际对象)利用对象的equals()方法检查两个对象是否相等
assertSame(期望对象,实际对象)检查具有相同内存地址的两个对象是否相等
assertNotSame(期望对象,实际对象)检查具有不同内存地址的两个对象是否不相等
assertNull(对象 对象)检查一个对象是否为空
assertNotNull(对象 对象)检查一个对象是否为非空
assertTrue(布尔条件)检查条件是否为真
assertFalse(布尔条件)检查条件是否为假

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值