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),并在终端上输入:
$ 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,以方便你在独立的线程中同时运行所有测试,并在所有线程中的测试都完成时停止。
利用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的文档。