JUnit 测试的组成

在这里我们主要展示除了上一章提到的基本的测试方法之外的几个很好用的测试方法。suite,setUp()和tearDown(),以及异常处理。

suite集合测试用例

如上一章在最后展示的JUnit测试骨架的最小要求,我们可以看到一个测试类包含一些测试方法,每个方法包含一个或者多个断言语句。但是,实际上测试类也能调用其他测试类:单独的类、包,甚至是完整的一个系统。而这种调用就是通过创建test suite来实现的。
test suite: 测试套件(test suite)有时也称为验证套件(validation suite),是许多测试用例的集合,测试用例可用来测试一程式是否正确工作,测试套件包括许多测试用例,一般也会有针对测试用例及其测试目的的详细说明,在进行测试时的系统组态资讯以及测试前需进行的步骤。简单来说,可以把测试套件理解为测试用例的集合

任何测试类都可以包含一个名为suite的静态方法,我们可以通过suite()方法来返回任何想要的测试集合,而没有suite()方法的话,JUnit会自动运行所有的test方法。

1
public static TEST suite

现在考虑这样一个例子:假设我们现在有一个普通的测试例子TestClassOne,我们希望执行其中所有的测试;有一个TestClassTwo我们希望执行其中的一部分测试,而对测试例子的选择就可以通过suite来实现。而且在TestClassTwo.java中我们可以看到给构造函数的String参数是做什么用的了:它让TestCase返回了一个对命名测试方法的引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
// TestClassOne.java
import junit.framework.*;
public class TestClassOne extends TestCase {
public TestClassOne(String method) {
super(method);
}
public void testadd() {
assertEquals(4,2+2);
}
public void testsub() {
assertEquals(0,2-2);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// TestClassTwo
import junit.framework.*;
public class TestClassTwo extends TestCase {
public TestClassTwo(String method) {
super(method);
}
public void testmul() {
assertEquals(4,2*2);
}
public void testdiv() {
assertEquals(0.667,2/3);
}
public static Test suite() {
TestSuite suite = new TestSuite();
//此时我们可以看到给构造函数的String参数让TestCase返回了一个对命名测试方法的引用
suite.addTest(new TestClassTwo("testmul"));
return suite;
}
}

在这里我们用TestClassComposite演示了用一个高一级别的测试来组合两个测试类的方法,这段代码将执行三个方法,分别是:testadd,testsub和testmul。testdiv被滤掉了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// TestClassComposite
import junit.framework.*;
public class TestClassComposite extends TestCase {
public TestClassComposite(String method) {
super(method);
}
static public Test suite() {
TestSuite suite = new TestSuite();
//include everything in TestClassOne
suite.addTestSuite(TestClassOne.class);
//use the suite method in TestClassTwo
suite.addTest(TestClassTwo.suite());
return suite;
}
}

Setup和Tear-down用于环境/资源的建立和清理

每个测试的运行都应该是相互独立的,在每个测试开始之前,会需要重新设置某些测试环境,在测试完成后释放这些资源。JUnit的TestCase提供了两个方法以供改写,分别用于环境/资源的建立和清理

1
2
protected void setUp();
protected void tearDown();

举个例子,如果我们的每个测试都需要进行数据库连接的话,借助于Setup和Tear-down就不用再每个测试方法中重复写数据库连接的语句,而只需在Setup和Tear-down中分别建立和释放连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Per-method的Setup和Tear-down用于环境/资源的建立和清理
public class TestDB extends TestCase {
private Connection dbConnection;
public TestDB(String method) {
super(method);
}
protected void setUp() {
dbConnection = new Connection("oracle",123456,"wtli");
dbConnection.connect();
}
protected void tearDown() {
dbConnection.disconnect();
dbConnection = null;
}
public void testAdminAccess() {
...
}
public void testGuestAccess() {
...
}
}

上例的执行过程为setUp()->testAdminAccess()->tearDown()->setUp()->testGuestAccess()->tearDown()。
一般情况下,我们只需按上例一样,为每个方法设置运行环境。但是在一些情况下,我们需要为整个test suite设置一些环境,以及在test suite中的所有方法都执行完成后做一些清理工作。此时我们需要比Per-method的Setup和Tear-down稍微复杂一点的,Per-suite的Setup和Tear-down。我们直接在上面的TestClassTwo做一些改变。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// Per-suite的Setup和Tear-down用于环境/资源的建立和清理
import junit.framework.*;
import junit.extensions.*;
public class TestClassTwo extends TestCase {
public TestClassTwo(String method) {
super(method);
}
public void testmul() {
assertEquals(4,2*2);
}
public void testdiv() {
assertEquals(0.667,2/3);
}
public static Test suite() {
TestSuite suite = new TestSuite();
/*此时我们可以看到给构造函数的String参数是做什么用的了:
它让TestCase返回了一个对命名测试方法的引用*/
suite.addTest(new TestClassTwo("testmul"));
TestSetup wrapper = new TestSetup(suite) {
protected void setUp() {
oneTimeSetup();
}
protected void tearDown() {
oneTimeTearDown();
}
};
return wrapper;
}
public static void oneTimeSetup() {
...
}
public static void oneTimeTearDown() {
...
}
}

可以看到在Per-suite的Setup和Tear-down中,需要提供所需测试的一个suite,并且把它包装进TestSetup对象。
代码的执行顺序

异常处理

如果你是从单元测试整理(二)——断言篇,首个单元测试程序看过来的,那么应该有印象,我们在第一个单元测试程序中对被测程序的抛出异常就做了测试

1
2
3
4
5
6
7
8
public void testisEmpty() {
try {
Largest.largest(new int[] {});
fail("An exception should been thrown!");
}catch(RuntimeException e) {
assertTrue(true);
}
}

被测试的代码被try/catch包含于内。预期这个方法会抛出一个异常,如果没有会把测试置为失败,如果异常如期发生,会跳到catch并且记录下断言以做统计使用。实际上在测试中异常是一个好东西,它可以告诉我们哪里出错了。而在这里选择assertTrue一是因为这个断言可以判断流程是否会如约到达这个地方,而且assertTrue没有被调用不会产生任何错误。

注释

注释就好像你可以在你的代码中添加并且在方法或者类中应用的元标签。JUnit 中的这些注释为我们提供了测试方法的相关信息,哪些方法将会在测试方法前后应用,哪些方法将会在所有方法前后应用,哪些方法将会在执行中被忽略。
JUnit 中的注释的列表以及他们的含义:

序号 注释 描述
1 @Test 这个注释说明依附在 JUnit 的 public void 方法可以作为一个测试案例
2 @Before 有些测试在运行前需要创造几个相似的对象。在 public void 方法加该注释
是因为该方法需要在 test 方法前运行
3 @After 如果你将外部资源在 Before 方法中分配,那么你需要在测试运行后释放他们。
在 public void 方法加该注释是因为该方法需要在 test 方法后运行
4 @BeforeClass 在 public void 方法加该注释是因为该方法需要在类中所有方法前运行
5 @AfterClass 它将会使方法在所有测试结束后执行。这个可以用来进行清理活动
6 @Ignore 这个注释是用来忽略有关不需要执行的测试的

其中:
beforeClass() 方法首先执行,并且只执行一次。
afterClass() 方法最后执行,并且只执行一次。
before() 方法针对每一个测试用例执行,但是是在执行测试用例之前。
after() 方法针对每一个测试用例执行,但是是在执行测试用例之后。
在 before() 方法和 after() 方法之间,执行每一个测试用例。

下一章单元测试整理(四)——测试哪些内容及边界条件将介绍用Right-BICEP确定测试哪些内容,用CORRECT找寻边界条件