十、 高级测试
现在,我将展示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的情况下,我需要一个特定的运行机来执行我的测试用例。这是为什么我显式地注解了它们。