引子

单元测试主要是用来验证所测代码是否和程序员的期望一致,如下所示,在实际操作中,我们可以编写一个函数assertTure()来验证预期条件是否满足,也可以进一步编写一个函数assertEquals()来判断两个数是否相等。事实上诸如此类判断函数,我们称之为断言。

1
2
3
4
5
6
7
8
9
public void assertTrue(boolean condition){
if(!condition){
abort();
}
}
--------------------------------------------
int a = 5;
//如果由于某种原因,当调用assertTrue()的时候,a并不等于5,上面程序将终止
assertTure(a == 5);

1
2
3
public void assertEquals(int a, int b){
assertTrue(a == b);
}

断言 是一种放在程序中的一阶逻辑(如一个结果为真或是假的逻辑判断式),目的是为了标示与验证程序开发者预期的结果-当程序运行到断言的位置时,对应的断言应该为真。若断言不为真时,程序会中止运行,并给出错误消息。

常用断言

序号 方法描述 描述
1 void assertEquals(boolean expected, boolean actual) 检查两个变量或者等式是否平衡
2 void assertTrue(boolean expected, boolean actual) 检查条件为真
3 void assertFalse(boolean condition) 检查条件为假
4 void assertNotNull(Object object) 检查对象不为空
5 void assertNull(Object object) 检查对象为空
6 void assertSame(boolean condition) 检查两个相关对象是否指向同一个对象
7 void assertNotSame(boolean condition) 检查两个相关对象是否不指向同一个对象
8 void assertArrayEquals(expectedArray, resultArray) 检查两个数组是否相等

上表列举了常用的八种断言,事实上我们在实际使用中并不需要自己编写这些基础的断言,java的编程框架JUnit已经集成了这些断言和一系列单元测试相关的函数。
JUnit 是一个 Java 编程语言的单元测试框架(回归测试框架)。JUnit 在测试驱动的开发方面有很重要的发展,是起源于 JUnit 的一个统称为 xUnit 的单元测试框架之一。JUnit 促进了“先测试后编码”的理念,强调建立测试数据的一段代码,可以先测试,然后再应用。这个方法就好比“测试一点,编码一点,测试一点,编码一点……”,增加了程序员的产量和程序的稳定性,可以减少程序员的压力和花费在排错上的时间。

第一个单元测试程序

我们将从这样一个简单的例子开始:查找一个list中的最大值。这次我们不妨用一种先想测试,再写代码的编程思想。
1,首先,这个程序要保证一个list,如{7,8,9},可以把9输出出来
2,然后呢,上面给了一个顺序的list,不失一般性,这个程序不应该是顺序敏感的,所以{7,9,8},{9,8,7}等也要输出9
3,我发现,上面的list都是正数,那么负数和0呢?{-7,-8,-9}应该返回-7吧。
4,等等,如果list里面有重复项呢?{7,9,8,9}应该只返回一个9就可以了
5,那么,如果list里面只有一个元素呢?{9}当然是返回9了
6,考虑完了?空值呢?如果list为空,{ }要怎么处理呢?
总结:1+2+5->注意上下界;3->max的初始值应为一个数型最小值;4->记录且return一个max;6->抛个异常
代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//Largest.java
public class Largest {
public static int largest (int[] list) {
int max=Integer.MIN_VALUE;
if(list.length == 0) {
throw new RuntimeException("Empty list");
}
for(int i=0;i<list.length;i++) {
if(list[i]>max) {
max = list[i];
}
}
return max;
}
}

事实上上述代码的测试代码就是考虑了最开始那6个测试例子,加以实现:

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
//LargestTest.java
import junit.framework.*;
public class LargestTest extends TestCase {
public LargestTest(String name) {
super(name);
}
//test numbers
public void testNums() {
//general test
assertEquals(9, Largest.largest(new int[] {7,8,9}));
//order test
assertEquals(9, Largest.largest(new int[] {7,9,8}));
assertEquals(9, Largest.largest(new int[] {9,8,7}));
//repetition test
assertEquals(9, Largest.largest(new int[] {7,9,8,9}));
//single test
assertEquals(9, Largest.largest(new int[] {9}));
//negative test
assertEquals(-7, Largest.largest(new int[] {-7,-8,-9}));
}
//test empty
public void testisEmpty() {
try {
Largest.largest(new int[] {});
fail("An exception should been thrown!");
}catch(RuntimeException e) {
assertTrue(true);
}
}
}

注:1,如果import junit.framework.*;报错是因为没有导入junit的jar包。可以Eclipse中:右键工程名–>属性–>Java Build Path–>Libraries–>Add Library,选中JUnit导入,版本随意。
2,相关代码可以直接在我的Github下载,欢迎star。

做一个简单的总结,测试代码必须要做以下几件事:
1,准备测试所需要的各种条件(创建所有必须的对象,分配必要的资源等等)
2,调用要测试的方法
3,验证被测试方法的行为和期望是否一致
4,完成后清理各种资源(第三章讲)

JUnit 测试骨架

下面是一段简单的测试代码,它展示了让一段断言方法所需要的骨架的最小要求

1
2
3
4
5
6
7
8
9
import junit.framework.*;
public class FrameTest extends TestCase {
public FrameTest(String name) {
super(name);
}
public void testFrame(){
...
}
}

尽管上面的代码非常简洁,还是要简单解释下每一部分
1,首先,第一行的import声明引入了必需的JUnit类。
2,接下来在第2行定义了一个类:每个包含测试的类都必须如上所示那样有TestCase继承而来。基类TestCase提供了我们所需的大部分单元测试功能,包括所有在前面讲述过的断言方法。
3,其中,基类需要一个以String为参数的构造函数,因而我们必须调用super来传递这么一个名字。我们不知道这个名字此时是什么,因而我们就仅仅让我们自己的构造函数接受String参数并把这个参数在第4行传递上去。
4,最后,测试类包含了名为test的方法。在上面这个例子中,我们在第6行写了一个名为testFrame的方法。而所有以test开头的方法都会被JUnit自动运行。当然,还可以通过定义suite方法制定特殊的函数来运行(第三章讲)。

在下一章单元测试整理(三)——JUnit 测试组成和注释将进一步展示更完整的JUnit骨架,介绍一些复杂一点的辅助测试方法。