毋庸置疑,程序员要对自己编写的代码负责,您不仅要保证它能通过编译,正常地运行,而且要满足需求和设计预期的效果。单元测试正是验证代码行为是否
满足预期的有效手段之一。但不可否认,做测试是件很枯燥无趣的事情,而一遍又一遍的测试则更是让人生畏的工作。幸运的是,单元测试工具
JUnit
使这一切变得简单艺术起来。
JUnit
是
Java
社区中知名度最高的单元测试工具。它诞生于
1997
年,由
Erich Gamma
和
Kent Beck
共同开发完成。其中
Erich Gamma
是经典著作《设计模式:可复用面向对象软件的基础》一书的作者之一,并在
Eclipse
中有很大的贡献;
Kent
Beck
则是一位极限编程(
XP
)方面的专家和先驱。
麻雀虽小,五脏俱全。
JUnit
设计的非常小巧,但是功能却非常强大。
Martin Fowler
如此评价
JUnit
:在软件开发领域,从来就没有如此少的代码起到了如此重要的作用。它大大简化了开发人员执行单元测试的难度,特别是
JUnit 4
使用
Java 5
中的注解(
annotation
)使测试变得更加简单。
在附件中的代码包中有生产代码
Money
类。请看以下对
Money
类的测试代码(所有测试代码也可以在附件的代码包中查找)
1 import static org.junit.Assert.assertTrue ;
2 import org.junit.Test;
3
4 public class TestSample {
5
6 @Test
7 public void add() {
8 Money m12CHF = new Money(12, "CHF" );
9
10 Money m14CHF = new Money(14, "CHF" );
11
12 Money expected = new Money(26, "CHF" );
13
14 Money result = m12CHF.add(m14CHF);
15
16 assertTrue (expected.equals(result));
17 }
18 }
19
在这段测试代码中有几点需要注意和
Junit3
测试代码不同之处:
1
该测试方法主要测试
Money
类中
add
()方法是否能正确对货币进行操作。在
Junit4
中对测试方法只要用
@Test
就标明该方法是测试方法。方法名字可以自行定义,不像
Junit3
中所有测试方法都要有
test
前缀。
2
测试类也不需要继承
TestCase
。
Junit4
中测试类不需要继承任何
Junit
中接口和类。
3
断言在
Junit4
中要静态导入。如本段代码中的
assertTrue
。
请先阅读一遍下列代码,然后我会对这段代码的一些
Junit4
特性进行详细说明。
1 import static org.junit.Assert.assertTrue ;
2 import java.util.ArrayList;
3 import org.junit.After;
4 import org.junit.Before;
5 import org.junit.Ignore;
6 import org.junit.Test;
7
8 public class TestFixture {
9 private Money f12CHF ;
10 private Money f14CHF ;
11 private Money f26CHF ;
12 private Money f28USD ;
13
14 @Before
15 public void setUp() {
16 f12CHF = new Money(12, "CHF" );
17 f14CHF = new Money(14, "CHF" );
18 f26CHF = new Money(26, "CHF" );
19 f28USD = new Money(28, "USD" );
20 }
21
22
23
24 @Test
25 public void testAdd() {
26 Money result = f12CHF .add(f14CHF );
27 assertTrue (f26CHF .equals(result));
28 }
29
30
31 @Ignore ("this test method isn't working now" )
32 @Test (expected = IndexOutOfBoundsException.class )
33 public void empty() {
34 new ArrayList<Object>().get(0);
35 }
36
37
38 @After
39 public void tearDown() {
40 f12CHF = null ;
41 f14CHF = null ;
42 f26CHF = null ;
43 f28USD = null ;
44 }
45
46
47 @Test (timeout = 1000)
48 public void testTimeOut() throws InterruptedException {
49 // wait(100);
50 Money result = f12CHF .add(f14CHF );
51 }
52 }
1
.
@Before
和
@After
在
Junit3
中对测试数据的初始化是放在
setUp
方法中,而对测试数据的重置和销毁是放在
tearDown
方法中。在
Junit4
中则可以使用
@Before
和
@After
来代替。在这段代码中方法名字还是使用
setUp
和
tearDown
是为了让有
Junit3
经验的开发者更好理解
@Before
和
@After
。其实在
Junit4
中这两个方法名可以自行定义为其他任何名字。
2
.
@Test
(
expected=XXXException.class
)
在
JUnit4.0
之前,对错误的测试,我们只能通过
fail
来产生一个错误,并在
try
块里面
assertTrue
(
true
)来测试。现在,通过
@Test
中的
expected
属性就可以实现了。
expected
属性的值是一个异常的类型,如代码中的
IndexOutOfBoundsException.class
。
(注释掉
@lgnore(“this test method isn't working
now
”)
,运行后可查看
)
3
.
@Ignore
有该标记的测试方法在测试中会被忽略。,你可以为该标签传递一个
String
的参数,来表明为什么会忽略这个测试方法。比如:
@lgnore(“this test method isn't working
now
”)
,在执行的时候,仅会报告该方法没有实现,而不会运行该测试方法。如代码中的
empty
()方法。
4.
@ Test
(
timeout =…
)
该
标记
传入了一个时间(毫秒)给测试方法,如果测试方法在指定的时间之内没有运行完,则测试会失败,即使被测试方法测试正确也会失败。该标记主要用于测试被测试方法运行所需时间,即用于方法的简单性能测试。(将
wait(100);注释后运行查看结果,再取消注释运行查看结果,对比两种结果
)
再看下列代码。
1 import static org.junit.Assert.assertTrue ;
2 import org.junit.AfterClass;
3 import org.junit.BeforeClass;
4 import org.junit.Test;
5
6 public class TestOnce {
7 private static Money f12CHF ;
8 private static Money f14CHF ;
9 private static Money f26CHF ;
10 private static Money f28USD ;
11
12
13 @BeforeClass
14 public static void setUp() {
15 f12CHF = new Money(12, "CHF" );
16 f14CHF = new Money(14, "CHF" );
17 f26CHF = new Money(26, "CHF" );
18 f28USD = new Money(28, "USD" );
19 }
20
21 @Test
22 public void testAdd() {
23 Money result = f12CHF .add(f14CHF );
24 assertTrue (f26CHF .equals(result));
25 }
26
27 @AfterClass
28 public static void TearDown() {
29 f12CHF = null ;
30 f14CHF = null ;
31 f26CHF = null ;
32 f28USD = null ;
33 }
34 }
5
.
@BeforeClass
和
@AfterClass
这里
@BeforeClass
和
@AfterClass
是为了在一个
Test
类的所有测试方法执行前后各执行一次。这是为了能在
@BeforeClass
中初始化一些昂贵的资源,例如数据库连接,然后执行所有的测试方法,最后在
@AfterClass
中释放资源。
正如你看到的,由于
@BeforeClass
和
@AfterClass
仅执行一次,因此它们只能标记静态方法,在所有测试方法中共享的资源也必须是静态引用。可仔细查看上述代码中对私有成员变量和标记
@BeforeClass
和
@AfterClass
的方法的类型。
有时候要对某一特定方法传入各种不同的参数值,用来测试这个方法的健壮性。在
Junit3
中必须为每种参数值单独写一个独立的测试方法。这样就造成很多测试代码测试的都是同一方法,只是传入的参数值有不同。在
Junit4
中只需要一个测试方法就能针对
N
种不同参数值进行
N
次测试。试看如下代码:
1 import static org.junit.Assert.assertTrue ;
2 import java.util.Arrays;
3 import java.util.Collection;
4 import org.junit.BeforeClass;
5 import org.junit.Test;
6 import org.junit.runner.RunWith;
7 import org.junit.runners.Parameterized;
8 import org.junit.runners.Parameterized.Parameters;
9
10 /**
11 * @author Administrator
12 * 第一个注意点
13 */
14 @RunWith (Parameterized.class )
15 public class TestParameter {
16 private static Money f12CHF ;
17 // 第二个注意点
18 private Money expected ;
19 private Money target ;
20
21 @BeforeClass
22 public static void setUp() {
23 f12CHF = new Money(12, "CHF" );
24 }
25
26 /**
27 * 第三个注意点
28 * @return
29 */
30 @Parameters
31 public static Collection words() {
32 return Arrays.asList (new Object[][] { { new Money(23, "CHF" ), new Money(11, "CHF" ) },
33 { new Money(28, "CHF" ), new Money(16, "CHF" ) }
34 });
35 }
36
37 /**
38 * 第四个注意点
39 * 参数化测试必须的构造函数
40 * @paramexpected 期望的测试结果,对应参数集中的第一个参数
41 * @paramtarget 测试数据,对应参数集中的第二个参数
42 */
43 public TestParameter(Money expected, Money target) {
44 this .expected = expected;
45 this .target = target;
46 }
47
48 /**
49 * 实际需要测试的方法
50 */
51 @Test
52 public void add() {
53 assertTrue (expected .equals(f12CHF .add(target )));
54 }
55 }
请详细查看注释中所标注的注意点。首先测试类需要声明
@RunWith(Parameterized.class)
。然后设置两个成员变量,一个是测试期望返回的值变量,如代码中
expected
,
还有一个是测试方法传入的参数值变量,如代码中
target
。针对这两个成员变量,新建包含这两个成员变量的测试类构造方法。再新建一个方法进行参数初始化。必须将该方法声明为
static
并返回一个
Collection
类型。需要用
@Parameters
注解来修饰该方法。在该方法内部,仅仅创建一个多维
Object
数组,并将该数组转换为
List
。然后运行
Junit
后,测试通过后的界面如下图:
由图可知执行该测试类时,通过
add
()
测试方法运行两次,将代码中
words
方法里的每个值对运行一次。
相比
Junit3
的套件测试,
Junit4
的套件测试只需要两个注解就可以运行了,而不需要写任何代码。代码示例如下:
1 import junit.framework.JUnit4TestAdapter;
2 import junit.framework.Test;
3 import org.junit.runner.RunWith;
4 import org.junit.runners.Suite;
5 import org.junit.runners.Suite.SuiteClasses;
6
7 @RunWith (Suite.class )
8 @SuiteClasses ( { TestSample.class , TestFixture.class , TestOnce.class , TestParameter.class })
9 public class TestSuite {
10 public static Test suite() {
11 return new JUnit4TestAdapter(TestSuite.class );
12 }
13 }
在
JUnit 4
中,套件被两个新注解所替代。第一个是
@RunWith
,设计它是为了方便让不同的运行器执行一个特别的测试类。
JUnit4
绑定一个叫做
Suite
的套件运行器,必须在
@RunWith
注释中指定这个运行器。不仅如此,还必须提供另一项叫做
@SuiteClasses
的注释,它将一个意欲表示测试套件的类列表作为参数。如代码所示,将需要测试的测试类依次放入
SuiteClasses
中就可以了。另外如果要使用ant1.7
之前的版本,则要像代码中所示对suite
方法进行编写。如果是1.7
后版本或者是使用maven
则删除suite
方法就可以了。