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中几个不得不掌握的方法
例如:只运行一个测试用例,您可能会执行:
运行两个测试用例,可以先产生一个套件(Suite),然后将这两个测试用例包含其中,如下:
suite.addTest( new MoneyTest( " testMoneyMoneyBag " ));
suite.addTest( new MoneyTest( " testSimpleAdd " ));
TestResult result = suite.run();
组装TestSuite,运行更多的test。在Junit中,Test、TestCase和TestSuite三者组成了composiste pattern。通过组装自己的TestSuite,可以完成对添加到这个TestSuite中的所有的TestCase的调用。而且这些定义的TestSuite还可以组装成更大的TestSuite,这样同时也方便了对于不断增加的TestCase的管理和维护。
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;
}
}
< 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 >
|---mod1
|---class1
junit < =测试代码根目录
|---com
|---mod1
|---class1
Method Summary | |
static void | assertArrayEquals(byte[] expecteds, byte[] actuals) |
static void | assertArrayEquals(char[] expecteds, char[] actuals) |
static void | assertArrayEquals(int[] expecteds, int[] actuals) |
static void | assertArrayEquals(long[] expecteds, long[] actuals) |
static void | assertArrayEquals(java.lang.Object[] expecteds, java.lang.Object[] actuals) |
static void | assertArrayEquals(short[] expecteds, short[] actuals) |
static void | assertArrayEquals(java.lang.String message, byte[] expecteds, byte[] actuals) |
static void | assertArrayEquals(java.lang.String message, char[] expecteds, char[] actuals) |
static void | assertArrayEquals(java.lang.String message, int[] expecteds, int[] actuals) |
static void | assertArrayEquals(java.lang.String message, long[] expecteds, long[] actuals) |
static void | assertArrayEquals(java.lang.String message, java.lang.Object[] expecteds, java.lang.Object[] actuals) |
static void | assertArrayEquals(java.lang.String message, short[] expecteds, short[] actuals) |
static void | assertEquals(double expected, double actual, double delta) |
static void | assertEquals(java.lang.Object[] expecteds, java.lang.Object[] actuals) |
static void | assertEquals(java.lang.Object expected, java.lang.Object actual) |
static void | assertEquals(java.lang.String message, double expected, double actual, double delta) |
static void | assertEquals(java.lang.String message, java.lang.Object[] expecteds, java.lang.Object[] actuals) |
static void | assertEquals(java.lang.String message, java.lang.Object expected, java.lang.Object actual) |
static void | assertFalse(boolean condition) |
static void | assertFalse(java.lang.String message, boolean condition) |
static void | assertNotNull(java.lang.Object object) |
static void | assertNotNull(java.lang.String message, java.lang.Object object) |
static void | assertNotSame(java.lang.Object unexpected, java.lang.Object actual) |
static void | assertNotSame(java.lang.String message, java.lang.Object unexpected, java.lang.Object actual) |
static void | assertNull(java.lang.Object object) |
static void | assertNull(java.lang.String message, java.lang.Object object) |
static void | assertSame(java.lang.Object expected, java.lang.Object actual) |
static void | assertSame(java.lang.String message, java.lang.Object expected, java.lang.Object actual) |
static void | assertTrue(boolean condition) |
static void | assertTrue(java.lang.String message, boolean condition) |
static void | fail() |
static void | fail(java.lang.String 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.java和IsoDateTest.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也不能找到这些测试类。
这并不是一种启动测试的方便方法,但幸运的是,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),并在终端上输入:
图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目录下。
将一个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,以方便你在独立的线程中同时运行所有测试,并在所有线程中的测试都完成时停止。
|