提要 本文将向你介绍如何实现从junit 3.8向junit 4的迁移。同时,还讨论junit 4中的一些新特征,特别包括对注解的使用;最后,简要介绍这个新版本的ide集成现状。
一、 引言
在本文开始,我将假定,你已经了解由kent beck和erich gamma发明的这个java单元测试框架并因此而略过必要的简介。所以,我将集中分析从junit 3.8到最新版本-junit 4的迁移过程以及其在ide和ant中的集成。
junit 4是一种与其之前的版本完全不同的api,它根据java 5.0中的新特征(注解,静态导入等)构建而成。如你所见,junit 4更简单、更丰富和更易于使用,而且它引入了更为灵活的初始化和清理工作,还有限时的和参数化测试用例。
代码实例最能说明问题。因此,在本文中,我将使用一个例子来展示不同的测试用例:一个计算器。该示例计算器很简单,效率并不高,甚至还有一些错误;它仅仅操作整数,并且把结果存储在一个静态变量中。substract方法并不返回一个有效的结果,而且也没有实现乘法运算,而且看上去在squareroot方法中还存在一个错误:无限循环。这些错误将帮助说明使用junit 4进行测试的有效性。你可以打开和关闭这个计算器,而且你可以清除这些结果。下面是其实现代码:
二、 迁移一个测试类
现在,我将把一个已经使用junit 3.8编写成的简单的测试类迁移到junit 4。这个类有一些缺陷:它没有测试所有的业务方法,而且看上去在testdivide方法中还存在一个错误(8/2不等于5)。因为还没有实现乘法运算功能,所以对其测试将被忽略。
下面,我们把两个版本的框架之间的差别以粗体显示出现于表格1中。
表格1.分别以junit 3.8和junit 4实现的caculatortest。
junit 3.8
junit 4
三、 包
首先,你可以看到,junit 4使用org.junit.*包而junit 3.8使用的是junit.framework.*。当然,为了向后兼容性起见,junit 4jar文件发行中加入了这两种包。
四、 继承
在中,测试类不必再扩展junit.framework.testcase;事实上,它们不必须扩展任何内容。但是,junit 4中使用的是注解。为了以一个测试用例方式执行,一个junit 4类中至少需要一个@test注解。例如,如果你仅使用@before和@after注解而没有至少提供一个@test方法来编写一个类,那么,当你试图执行它时将得到一个错误:
java.lang.exception: no runnable methods.
五、 断言(assert)方法
因为在junit 4中一个测试类并不继承自testcase(在junit 3.8中,这个类中定义了assertequals()方法),所以你必须使用前缀语法(举例来说,assert.assertequals())或者(由于jdk5.0)静态地导入assert类。这样以来,你就可以完全象以前一样使用assertequals方法(举例来说,assertequals())。
另外,在junit 4中,还引入了两个新的断言方法,它们专门用于数组对象的比较。如果两个数组包含的元素都相等,那么这两个数组就是相等的。
由于jdk 5.0的自动装箱机制的出现,原先的12个assertequals方法全部去掉了。例如,原先junit 3.8中的assertequals(long,long)方法在junit 4中要使用assertequals(object,object)。对于assertequals(byte,byte)、assertequals(int,int)等也是这样。这种改进将有助于避免反模式。
在junit 4中,新集成了一个assert关键字(见我们的例子中的divide()方法)。你可以象使用assertequals方法一样来使用它,因为它们都抛出相同的异常(java.lang.assertionerror)。junit 3.8的assertequals将抛出一个junit.framework.assertionfailederror。注意,当使用assert时,你必须指定java的"-ea"参数;否则,断言将被忽略。
六、 预设环境(fixture)
fixture是在测试期间初始化和释放任何普通对象的方法。在junit 3.8中,你要使用setup()来实现运行每一个测试前的初始化工作,然后使用teardown()来进行每个测试后的清理。这两个方法在testcase类中都得到重载,因此都被唯一定义。注意,我在这个setup方法使用的是java5.0内置的@override注解-这个注解指示该方法声明要重载在超类中的方法声明。在junit 4中,则代之使用的是@before和@after注解;而且,可以以任何命名(在我们的例子中是clearcalculator())来调用这些方法。在本文后面,我将更多地解释这些注解。
七、 测试
junit 3.8通过分析它的签名来识别一个测试方法:方法名必须以"test"为前缀,它必须返回void,而且它必须没有任何参数(举例来说,publicvoidtestdivide())。一个不遵循这个命名约定的测试方法将被框架简单地忽略,而且不抛出任何异常(指示发生了一个错误)。
junit 4不使用与junit 3.8相同的约定。一个测试方法不必以'test'为前缀,但是要使用@test注解。但是,正如在前一个框架中一样,一个测试方法也必须返回void并且是无参数的。在junit 4中,可以在运行时刻控制这个要求,并且不符合要求的话会抛出一个异常:
java.lang.exception: method xxx should have no parameters
java.lang.exception: method xxx should be void
@test注解支持可选参数。它声明一个测试方法应该抛出一个异常。如果它不抛出或者如果它抛出一个与事先声明的不同的异常,那么该测试失败。在我们的例子中,一个整数被零除应该引发一个arithmeticexception异常。
八、 忽略一个测试
记住,不能执行多个方法。然而,如果你不想让测试失败的话,你可以仅仅忽略它。那么,在junit 3.8中,我们是如何实现临时禁止一个测试的呢?方法是:通过注释掉它或者改变命名约定,这样测试运行机就无法找到它。在我的例子中,我使用了方法名notreadyyettestmultiply()。它没有以"test"开头,所以它不会被识别出来。现在的问题是,在成百上千的测试中间,你可能记不住重命名这个方法。
在junit 4中,为了忽略一个测试,你可以注释掉一个方法或者删除@test注解(你不能再改变命名约定,否则将抛出一个异常)。然而,该问题将保留:该运行机将不报告这样一个测试。现在,你可以把@ignore注解添加到@test的前面或者后面。测试运行机将报告被忽略的测试的个数,以及运行的测试的数目和运行失败的测试数目。注意,@ignore使用一个可选参数(一个string),如果你想记录为什么一个测试被忽略的话。
九、 运行测试
在junit 3.8中,你可以选择使用若干运行机:文本型,awt或者swing。junit 4仅仅使用文本测试运行机。注意,junit 4不会显示任何绿色条来通知你测试成功了。如果你想看到任何类型的绿色的话,那么你可能需要使用junit扩展或一种集成了junit的ide(例如idea或者eclipse)。
首先,我想使用老式但好用的junit.textui.testrunner来运行该junit 3.8测试类(考虑到使用assert关键字,我使用了-ea参数)。
java -ea junit.textui.testrunner junit3.calculatortest
testdivide产生一个错误,因为断言确定了8/2不等于5。testsubstract产生一个失败,因为10-2应该等于8,但是在这个实现中存在一个错误:它返回9。
现在,我使用新的org.junit.runner.junitcore运行机来运行这两个类。注意,它能执行junit 4和junit 3.8测试,甚至是这二者的结合。
第一个非常明显的区别是,junit版本号被显示于控制台中(4.1)。第二个区别是,junit 3.8区分失败和错误;junit 4则仅使用失败进行简化。一个新奇的地方是,字母"i",它显示一个测试被忽略。 十、 高级测试
现在,我将展示junit 4的一些高级特征。列表1(见下载源码)是一个新的测试类-advancedtest,它派生自abstractparent。
(一) 高级预设环境
两个类都使用新的注解@beforeclass和@afterclass,还有@before和@after。表格2展示了在这些注解之间的主要区别。
表格2.@beforeclass/@afterclass比较于@before/@after。
如果你仅有一次需要分配和释放昂贵的资源,那么@beforeclass和@afterclass可能很有用。在我们的例子中,abstractparent使用这些在starttestsystem()和stoptestsystem()方法上的注解启动和停止整个测试系统。并且它使用@before和@after初始化和清除系统。子类advancedtest也混合使用这些注解。
在你的测试代码中使用system.out.println不是一种良好的实践习惯;但是,在这个用例中,它有助于理解这些注解被调用的顺序。当我运行advancedtest时,我得到如下结果:
如你所见,@beforeclass和@afterclass仅被调用一次,而@before和@afterare在每次测试中都要调用。
(二) 限时测试
在前面的例子中,我为squareroot()方法编写了一个测试用例。记住,在这个方法中存在一个错误-能够导致它无限循环。如果没有结果的话,我想让这个测试在1秒钟后退出。这一功能正是timeout参数所要实现的。@test注解的第二个可选参数(第一个参数是必需的)可以使一个测试失败,如果该测试花费比一个预先确定的时限(毫秒)还长的时间的话。当我运行该测试时,我得到如下的运行结果:
(三) 参数化测试
在列表1中,我测试了squareroot(它是square方法而不是squareroot方法)-通过创建若干测试方法(square2,square4,square5),这些方法都完成相同的事情(通过被一些变量参数化实现)。其实,现在这里的复制/粘贴技术可以通过使用一个参数化测试用例加以优化(列表2)。
在列表2(见本文相应下载源码)中的测试用例使用了两个新的注解。当一个类被使用@runwith注释时,junit将调用被参考的类来运行该测试而不是使用缺省的运行机。为了使用一个参数化测试用例,你需要使用运行机org.junit.runners.parameterized。为了确定使用哪个参数,该测试用例需要一个公共静态方法(在此是data(),但是名字似乎无关),该方法返回一个collection,并且被使用@参数加以注解。你还需要一个使用这些参数的公共构造函数。
当运行这个类,该输出是:
在此,共执行了7个测试,好象编写了7个单独的square方法。注意,在我们的测试中出现了一个失败,因为7的平方是49,而不是48。
(四) 测试集
为了在junit 3.8的一个测试集中运行若干测试类,你必须在你的类中添加一个suite()方法。而在junit 4中,你可以使用注解来代之。为了运行calculatortest和squaretest,你需要使用@runwith和@suite注解编写一个空类。
在此,@runwith注解告诉junit它使用org.junit.runner.suite。这个运行机允许你手工地构建一个包含测试(可能来自许多类)的测试集。这些类的名称都被定义在@suite.suiteclass中。当你运行这个类时,它将运行calculatortest和squaretest。其输出是:
(五) 测试运行机
在junit 4中,广泛地使用测试运行机。如果没有指定@runwith,那么你的类仍然会使用一个默认运行机(org.junit.internal.runners.testclassrunner)执行。注意,最初的calculator类中并没有显式地声明一个测试运行机;因此,它使用的是默认运行机。一个包含一个带有@test的方法的类都隐含地拥有一个@runwith。事实上,你可以把下列代码添加到calculator类上,而且其输出结果会完全一样。
在@parameterized和@suite的情况下,我需要一个特定的运行机来执行我的测试用例。这就是为什么我显式地注解了它们。 十一、 工具集成功能
当我写本文时,junit 4在ide方面的集成还不是很理想。事实上,如果你试图运行我们刚才看到的那个测试类的话,它们无法工作在任何ide环境中,因为它们不能被识别为测试类。为了向前兼容性起见,junit 4发行中带有一个适配器(junit.framework.junit 4testadapter),你必须把它使用于一个suite()方法中。下面是你必须添加到每个类中的代码;这样以来,它们才能为各种ide,ant以及junit 3.8中的文本运行机所识别:
(一) intellij idea
idea 5并没有集成junit 4。没有办法,我们只好等待idea 6中实现这一集成了。在这个例子中,我使用了较早的发行版本(demetra build 5321);但是,参数化测试用例仍不能工作。图1展示了这个calculatortest的执行情况(被忽略的测试以一个不同的图标标志)。
(二) eclipse
我现在使用的是eclipse的3.2 rc7版本。虽然它还不是一个稳定发行版本,但是其与junit 4的集成优于idea。上图2展示了在运行allcalculatortests类时你能看到的结果。
如你所见,该参数化测试用例(squaretest)被描述为7个单独的测试。
(三) ant集成
junit任务当前仅仅支持junit 3.8风格测试;这也就是说,你还必须用一个junit 4 testadapter来包装你的junit 4测试;这样,它们才能在ant中运行。这个<junit>任务与其在junit 3.8中用法一样:
十二、 结论
有很长一段时间,junit简直成了事实上的单元测试框架标准。但是,近来,这个框架似乎无大"动静":没有重要的发行版本,没有引人注目的新特征出现。这可能是为什么其它测试框架,例如test-ng开始逐渐占居测试框架市场统治地位的原因。
随着这个新版本的发行,junit又出现了新的转机。如今,它提供了许多新的api,而且现在还使用注解,所以使开发测试用例更为容易。事实上,该junit开发者已经开始考虑新的未来的注解问题。例如,你可以在一个依赖于前提(举例来说,你需要在线地执行这个测试)的测试用例上添加一个@prerequisite注解;或者添加一个能够指定重复次数及时限(举例来说,重复测试5次以确保真正出现了一个时限问题)的@repeat注解;或者甚至在@ignore注解上添加一个平台参数(举例来说,@ignore(platform=macos),这将只有你在一个macos平台上运行时才忽略一个测试)。从本文中你能看到,junit的未来依然灿烂辉煌。
一、 引言
在本文开始,我将假定,你已经了解由kent beck和erich gamma发明的这个java单元测试框架并因此而略过必要的简介。所以,我将集中分析从junit 3.8到最新版本-junit 4的迁移过程以及其在ide和ant中的集成。
junit 4是一种与其之前的版本完全不同的api,它根据java 5.0中的新特征(注解,静态导入等)构建而成。如你所见,junit 4更简单、更丰富和更易于使用,而且它引入了更为灵活的初始化和清理工作,还有限时的和参数化测试用例。
代码实例最能说明问题。因此,在本文中,我将使用一个例子来展示不同的测试用例:一个计算器。该示例计算器很简单,效率并不高,甚至还有一些错误;它仅仅操作整数,并且把结果存储在一个静态变量中。substract方法并不返回一个有效的结果,而且也没有实现乘法运算,而且看上去在squareroot方法中还存在一个错误:无限循环。这些错误将帮助说明使用junit 4进行测试的有效性。你可以打开和关闭这个计算器,而且你可以清除这些结果。下面是其实现代码:
| package calc; public class calculator { private static int result; //存储结果的静态变量 public void add(int n) { result = result + n; } public void substract(int n) { result = result - 1; //错误:应该是"result = result - n" } public void multiply(int n) {} //还没实现 public void divide(int n) { result = result / n; } public void square(int n) { result = n * n; } public void squareroot(int n) { for (; ;) ; //错误:无限循环 } public void clear() { //清除结果 result = 0; } public void switchon() { //打开屏幕,显示"hello",并报警 result = 0; //实现其它的计算器功能 } public void switchoff() { } //显示"bye bye",报警,并关闭屏幕 public int getresult() { return result; } } |
二、 迁移一个测试类
现在,我将把一个已经使用junit 3.8编写成的简单的测试类迁移到junit 4。这个类有一些缺陷:它没有测试所有的业务方法,而且看上去在testdivide方法中还存在一个错误(8/2不等于5)。因为还没有实现乘法运算功能,所以对其测试将被忽略。
下面,我们把两个版本的框架之间的差别以粗体显示出现于表格1中。
表格1.分别以junit 3.8和junit 4实现的caculatortest。
junit 3.8
| package junit3; import calc.calculator; import junit.framework.testcase; public class calculatortest extends testcase { private static calculator calculator = new calculator(); @override protected void setup() { calculator.clear(); } public void testadd() { calculator.add(1); calculator.add(1); assertequals(calculator.getresult(), 2); } public void testsubtract() { calculator.add(10); calculator.subtract(2); assertequals(calculator.getresult(), 8); } public void testdivide() { calculator.add(8); calculator.divide(2); assert calculator.getresult() == 5; } public void testdividebyzero() { try { calculator.divide(0); fail(); } catch (arithmeticexception e) { } } public void notreadyyettestmultiply() { calculator.add(10); calculator.multiply(10); assertequals(calculator.getresult(), 100); } } |
junit 4
| package junit 4; import calc.calculator; import org.junit.before; import org.junit.ignore; import org.junit.test; import static org.junit.assert.*; public class calculatortest { private static calculator calculator = new calculator(); @before public void clearcalculator() { calculator.clear(); } @test public void add() { calculator.add(1); calculator.add(1); assertequals(calculator.getresult(), 2); } @test public void subtract() { calculator.add(10); calculator.subtract(2); assertequals(calculator.getresult(), 8); } @test public void divide() { calculator.add(8); calculator.divide(2); assert calculator.getresult() == 5; } @test(expected = arithmeticexception.class) public void dividebyzero() { calculator.divide(0); } @ignore("not ready yet") @test public void multiply() { calculator.add(10); calculator.multiply(10); assertequals(calculator.getresult(), 100); } } |
首先,你可以看到,junit 4使用org.junit.*包而junit 3.8使用的是junit.framework.*。当然,为了向后兼容性起见,junit 4jar文件发行中加入了这两种包。
四、 继承
在中,测试类不必再扩展junit.framework.testcase;事实上,它们不必须扩展任何内容。但是,junit 4中使用的是注解。为了以一个测试用例方式执行,一个junit 4类中至少需要一个@test注解。例如,如果你仅使用@before和@after注解而没有至少提供一个@test方法来编写一个类,那么,当你试图执行它时将得到一个错误:
java.lang.exception: no runnable methods.
五、 断言(assert)方法
因为在junit 4中一个测试类并不继承自testcase(在junit 3.8中,这个类中定义了assertequals()方法),所以你必须使用前缀语法(举例来说,assert.assertequals())或者(由于jdk5.0)静态地导入assert类。这样以来,你就可以完全象以前一样使用assertequals方法(举例来说,assertequals())。
另外,在junit 4中,还引入了两个新的断言方法,它们专门用于数组对象的比较。如果两个数组包含的元素都相等,那么这两个数组就是相等的。
| public static void assertequals(string message, object[] expecteds, object[] actuals); public static void assertequals(object[] expecteds, object[] actuals); |
由于jdk 5.0的自动装箱机制的出现,原先的12个assertequals方法全部去掉了。例如,原先junit 3.8中的assertequals(long,long)方法在junit 4中要使用assertequals(object,object)。对于assertequals(byte,byte)、assertequals(int,int)等也是这样。这种改进将有助于避免反模式。
在junit 4中,新集成了一个assert关键字(见我们的例子中的divide()方法)。你可以象使用assertequals方法一样来使用它,因为它们都抛出相同的异常(java.lang.assertionerror)。junit 3.8的assertequals将抛出一个junit.framework.assertionfailederror。注意,当使用assert时,你必须指定java的"-ea"参数;否则,断言将被忽略。
六、 预设环境(fixture)
fixture是在测试期间初始化和释放任何普通对象的方法。在junit 3.8中,你要使用setup()来实现运行每一个测试前的初始化工作,然后使用teardown()来进行每个测试后的清理。这两个方法在testcase类中都得到重载,因此都被唯一定义。注意,我在这个setup方法使用的是java5.0内置的@override注解-这个注解指示该方法声明要重载在超类中的方法声明。在junit 4中,则代之使用的是@before和@after注解;而且,可以以任何命名(在我们的例子中是clearcalculator())来调用这些方法。在本文后面,我将更多地解释这些注解。
七、 测试
junit 3.8通过分析它的签名来识别一个测试方法:方法名必须以"test"为前缀,它必须返回void,而且它必须没有任何参数(举例来说,publicvoidtestdivide())。一个不遵循这个命名约定的测试方法将被框架简单地忽略,而且不抛出任何异常(指示发生了一个错误)。
junit 4不使用与junit 3.8相同的约定。一个测试方法不必以'test'为前缀,但是要使用@test注解。但是,正如在前一个框架中一样,一个测试方法也必须返回void并且是无参数的。在junit 4中,可以在运行时刻控制这个要求,并且不符合要求的话会抛出一个异常:
java.lang.exception: method xxx should have no parameters
java.lang.exception: method xxx should be void
@test注解支持可选参数。它声明一个测试方法应该抛出一个异常。如果它不抛出或者如果它抛出一个与事先声明的不同的异常,那么该测试失败。在我们的例子中,一个整数被零除应该引发一个arithmeticexception异常。
八、 忽略一个测试
记住,不能执行多个方法。然而,如果你不想让测试失败的话,你可以仅仅忽略它。那么,在junit 3.8中,我们是如何实现临时禁止一个测试的呢?方法是:通过注释掉它或者改变命名约定,这样测试运行机就无法找到它。在我的例子中,我使用了方法名notreadyyettestmultiply()。它没有以"test"开头,所以它不会被识别出来。现在的问题是,在成百上千的测试中间,你可能记不住重命名这个方法。
在junit 4中,为了忽略一个测试,你可以注释掉一个方法或者删除@test注解(你不能再改变命名约定,否则将抛出一个异常)。然而,该问题将保留:该运行机将不报告这样一个测试。现在,你可以把@ignore注解添加到@test的前面或者后面。测试运行机将报告被忽略的测试的个数,以及运行的测试的数目和运行失败的测试数目。注意,@ignore使用一个可选参数(一个string),如果你想记录为什么一个测试被忽略的话。
九、 运行测试
在junit 3.8中,你可以选择使用若干运行机:文本型,awt或者swing。junit 4仅仅使用文本测试运行机。注意,junit 4不会显示任何绿色条来通知你测试成功了。如果你想看到任何类型的绿色的话,那么你可能需要使用junit扩展或一种集成了junit的ide(例如idea或者eclipse)。
首先,我想使用老式但好用的junit.textui.testrunner来运行该junit 3.8测试类(考虑到使用assert关键字,我使用了-ea参数)。
java -ea junit.textui.testrunner junit3.calculatortest
| ..f.e. there was 1 error: 1) testdivide(junit3.calculatortest)java.lang.assertionerror at junit3.calculatortest.testdivide(calculatortest.java:33) there was 1 failure: 1) testsubtract(junit3.calculatortest)junit.framework.assertionfailederror: expected:<9> but was:<8> at junit3.calculatortest.testsubtract(calculatortest.java:27) failures!!! tests run: 4, failures: 1, errors: 1 |
testdivide产生一个错误,因为断言确定了8/2不等于5。testsubstract产生一个失败,因为10-2应该等于8,但是在这个实现中存在一个错误:它返回9。
现在,我使用新的org.junit.runner.junitcore运行机来运行这两个类。注意,它能执行junit 4和junit 3.8测试,甚至是这二者的结合。
| java -ea org.junit.runner.junitcore junit3.calculatortest junit version 4.1 ..e.e. there were 2 failures: 1) testsubtract(junit3.calculatortest) junit.framework.assertionfailederror: expected:<9> but was:<8> at junit.framework.assert.fail(assert.java:47) 2) testdivide(junit3.calculatortest) java.lang.assertionerror at junit3.calculatortest.testdivide(calculatortest.java:33) failures!!! tests run: 4, failures: 2 *** java -ea org.junit.runner.junitcore junit 4.calculatortest junit version 4.1 ...e.ei there were 2 failures: 1) subtract(junit 4.calculatortest) java.lang.assertionerror: expected:<9> but was:<8> at org.junit.assert.fail(assert.java:69) 2) divide(junit 4.calculatortest) java.lang.assertionerror at junit 4.calculatortest.divide(calculatortest.java:40) failures!!! tests run: 4, failures: 2 |
第一个非常明显的区别是,junit版本号被显示于控制台中(4.1)。第二个区别是,junit 3.8区分失败和错误;junit 4则仅使用失败进行简化。一个新奇的地方是,字母"i",它显示一个测试被忽略。 十、 高级测试
现在,我将展示junit 4的一些高级特征。列表1(见下载源码)是一个新的测试类-advancedtest,它派生自abstractparent。
(一) 高级预设环境
两个类都使用新的注解@beforeclass和@afterclass,还有@before和@after。表格2展示了在这些注解之间的主要区别。
表格2.@beforeclass/@afterclass比较于@before/@after。
| @beforeclass和@afterclass | @before和@after |
| 在每个类中只有一个方法能被注解。 | 多个方法能被注解,但其执行的顺序未特别指定,且不运行重载方法。 |
| 方法名是不相关的 | 方法名是不相关的 |
| 每个类运行一次 | 在每个测试方法运行前或运行后运行 |
| 在当前类的@beforeclass方法运行前先运行超类的@beforeclass方法。在超类中声明的@afterclass方法将在所有当前类的该方法运行后才运行。 | 超类中的@before在所有子类的该方法运行前运行。在超类中的@after在在所有子类的该方法运行后才运行。 |
| 必须是公共和非静态的。 | 必须是公共和非静态的。 |
| 即使一个@beforeclass方法抛出一个异常,所有的@afterclass方法也保证被运行。 | 即使一个@before或者@test方法抛出一个异常,所有的@after方法也保证被运行。 |
如果你仅有一次需要分配和释放昂贵的资源,那么@beforeclass和@afterclass可能很有用。在我们的例子中,abstractparent使用这些在starttestsystem()和stoptestsystem()方法上的注解启动和停止整个测试系统。并且它使用@before和@after初始化和清除系统。子类advancedtest也混合使用这些注解。
在你的测试代码中使用system.out.println不是一种良好的实践习惯;但是,在这个用例中,它有助于理解这些注解被调用的顺序。当我运行advancedtest时,我得到如下结果:
| start test system //父类的@beforeclass switch on calculator //子类的@beforeclass initialize test system //第一个测试 clear calculator initialize test system //第二个测试 clear calculator clean test system initialize test system //第三个测试 clear calculator clean test system initialize test system //第四个测试 clear calculator clean test system switch off calculator //子类的@afterclass stop test system //父类的@afterclass |
如你所见,@beforeclass和@afterclass仅被调用一次,而@before和@afterare在每次测试中都要调用。
(二) 限时测试
在前面的例子中,我为squareroot()方法编写了一个测试用例。记住,在这个方法中存在一个错误-能够导致它无限循环。如果没有结果的话,我想让这个测试在1秒钟后退出。这一功能正是timeout参数所要实现的。@test注解的第二个可选参数(第一个参数是必需的)可以使一个测试失败,如果该测试花费比一个预先确定的时限(毫秒)还长的时间的话。当我运行该测试时,我得到如下的运行结果:
| there was 1 failure: 1) squareroot(junit 4.advancedtest) java.lang.exception: test timed out after 1000 milliseconds at org.junit.internal.runners.testmethodrunner.runwithtimeout(testmethodrunner.java:68) at org.junit.internal.runners.testmethodrunner.运行(testmethodrunner.java:43) failures!!! tests run: 4, failures: 1 |
(三) 参数化测试
在列表1中,我测试了squareroot(它是square方法而不是squareroot方法)-通过创建若干测试方法(square2,square4,square5),这些方法都完成相同的事情(通过被一些变量参数化实现)。其实,现在这里的复制/粘贴技术可以通过使用一个参数化测试用例加以优化(列表2)。
在列表2(见本文相应下载源码)中的测试用例使用了两个新的注解。当一个类被使用@runwith注释时,junit将调用被参考的类来运行该测试而不是使用缺省的运行机。为了使用一个参数化测试用例,你需要使用运行机org.junit.runners.parameterized。为了确定使用哪个参数,该测试用例需要一个公共静态方法(在此是data(),但是名字似乎无关),该方法返回一个collection,并且被使用@参数加以注解。你还需要一个使用这些参数的公共构造函数。
当运行这个类,该输出是:
| java org.junit.runner.junitcore junit 4.squaretest junit version 4.1 .......e there was 1 failure: 1) square[6](junit 4.squaretest) java.lang.assertionerror: expected:<48> but was:<49> at org.junit.assert.fail(assert.java:69) failures!!! tests run: 7, failures: 1 |
在此,共执行了7个测试,好象编写了7个单独的square方法。注意,在我们的测试中出现了一个失败,因为7的平方是49,而不是48。
(四) 测试集
为了在junit 3.8的一个测试集中运行若干测试类,你必须在你的类中添加一个suite()方法。而在junit 4中,你可以使用注解来代之。为了运行calculatortest和squaretest,你需要使用@runwith和@suite注解编写一个空类。
| package junit 4; import org.junit.runner.runwith; import org.junit.runners.suite; @runwith(suite.class) @suite.suiteclasses({ calculatortest.class, squaretest.class }) public class allcalculatortests {} |
在此,@runwith注解告诉junit它使用org.junit.runner.suite。这个运行机允许你手工地构建一个包含测试(可能来自许多类)的测试集。这些类的名称都被定义在@suite.suiteclass中。当你运行这个类时,它将运行calculatortest和squaretest。其输出是:
| java -ea org.junit.runner.junitcore junit 4.allcalculatortests junit version 4.1 ...e.ei.......e there were 3 failures: 1) subtract(junit 4.calculatortest) java.lang.assertionerror: expected:<9> but was:<8> at org.junit.assert.fail(assert.java:69) 2) divide(junit 4.calculatortest) java.lang.assertionerror at junit 4.calculatortest.divide(calculatortest.java:40) 3) square[6](junit 4.squaretest) java.lang.assertionerror: expected:<48> but was:<49> at org.junit.assert.fail(assert.java:69) failures!!! tests run: 11, failures: 3 |
(五) 测试运行机
在junit 4中,广泛地使用测试运行机。如果没有指定@runwith,那么你的类仍然会使用一个默认运行机(org.junit.internal.runners.testclassrunner)执行。注意,最初的calculator类中并没有显式地声明一个测试运行机;因此,它使用的是默认运行机。一个包含一个带有@test的方法的类都隐含地拥有一个@runwith。事实上,你可以把下列代码添加到calculator类上,而且其输出结果会完全一样。
| import org.junit.internal.runners.testclassrunner; import org.junit.runner.runwith; @runwith(testclassrunner.class) public class calculatortest { ... } |
在@parameterized和@suite的情况下,我需要一个特定的运行机来执行我的测试用例。这就是为什么我显式地注解了它们。 十一、 工具集成功能
当我写本文时,junit 4在ide方面的集成还不是很理想。事实上,如果你试图运行我们刚才看到的那个测试类的话,它们无法工作在任何ide环境中,因为它们不能被识别为测试类。为了向前兼容性起见,junit 4发行中带有一个适配器(junit.framework.junit 4testadapter),你必须把它使用于一个suite()方法中。下面是你必须添加到每个类中的代码;这样以来,它们才能为各种ide,ant以及junit 3.8中的文本运行机所识别:
| public static junit.framework.test suite() { return new junit 4testadapter(calculatortest.class); } |
(一) intellij idea
idea 5并没有集成junit 4。没有办法,我们只好等待idea 6中实现这一集成了。在这个例子中,我使用了较早的发行版本(demetra build 5321);但是,参数化测试用例仍不能工作。图1展示了这个calculatortest的执行情况(被忽略的测试以一个不同的图标标志)。
![]() 图1.ideademetra仅能运行calculatortest。 ![]() 图2.eclipse 3.2rc7能够运行测试集类allcalculatortests。 |
(二) eclipse
我现在使用的是eclipse的3.2 rc7版本。虽然它还不是一个稳定发行版本,但是其与junit 4的集成优于idea。上图2展示了在运行allcalculatortests类时你能看到的结果。
如你所见,该参数化测试用例(squaretest)被描述为7个单独的测试。
(三) ant集成
junit任务当前仅仅支持junit 3.8风格测试;这也就是说,你还必须用一个junit 4 testadapter来包装你的junit 4测试;这样,它们才能在ant中运行。这个<junit>任务与其在junit 3.8中用法一样:
| <!-- test --> <target name="test" depends="compile"> <junit fork="yes" haltonfailure="yes"> <test name=" junit 4.allcalculatortests"/> <formatter type="plain" usefile="false"/> <classpath refid="classpath"/> </junit> </target> |
十二、 结论
有很长一段时间,junit简直成了事实上的单元测试框架标准。但是,近来,这个框架似乎无大"动静":没有重要的发行版本,没有引人注目的新特征出现。这可能是为什么其它测试框架,例如test-ng开始逐渐占居测试框架市场统治地位的原因。
随着这个新版本的发行,junit又出现了新的转机。如今,它提供了许多新的api,而且现在还使用注解,所以使开发测试用例更为容易。事实上,该junit开发者已经开始考虑新的未来的注解问题。例如,你可以在一个依赖于前提(举例来说,你需要在线地执行这个测试)的测试用例上添加一个@prerequisite注解;或者添加一个能够指定重复次数及时限(举例来说,重复测试5次以确保真正出现了一个时限问题)的@repeat注解;或者甚至在@ignore注解上添加一个平台参数(举例来说,@ignore(platform=macos),这将只有你在一个macos平台上运行时才忽略一个测试)。从本文中你能看到,junit的未来依然灿烂辉煌。


闽公网安备 35060202000074号