大家有没有想过这个问题:当你把测试代码提交给JUnit框架后,框架如何来运行你的代码呢?答案就是——Runner。在JUnit中有很多个Runner,他们负责调用你的测试代码,每一个Runner都有各自的特殊功能,你要根据需要选择不同的Runner来运行你的测试代码。可能你会觉得奇怪,前面我们写了那么多测试,并没有明确指定一个Runner啊?这是因为JUnit中有一个默认Runner,即BlockJUnit4ClassRunner,如果你没有指定,那么系统自动使用默认Runner来运行你的代码。
以第一章节的测试代码为例:
package com.rigel.ut;
import org.junit.Test;
public class CalculatorTest {
@Test
public void testAdd() {
// ...
}
}
package com.rigel.ut;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.internal.runners.BlockJUnit4ClassRunner
;
@RunWith(BlockJUnit4ClassRunner
.class)
public class CalculatorTest {
@Test
public void testAdd() {
// ...
}
}
一般情况下,默认测试运行器可以应对绝大多数的单元测试要求;当使用 JUnit
提供的一些高级特性(例如即将介绍的两个特性)或者针对特殊需求定制JUnit测试方式时,显式的声明测试运行器就必不可少了。在Junit4
中,Junit为我们定义好了一些运行器,他们的层次结构如下:
其中ParentRunner是一个抽象类,包含了一个Runner的List,可以负责多个Runner运行。Suite是
一个测试套,继承了ParentRunner。BlockJUnit4ClassRunner则是Juint4默认的运行器,叫成Block是因为他是按
顺序一个一个执行测试用例的,Junit4中没有提供实现多线程执行的运行器。Junit4这个运行器只是单纯继承了
BlockJUnit4ClassRunner没有对其进行更改,并且它是final类型的,不允许继续被继承。Enclosed是实现内部类的测试类的运行器。Parameterized则可以设置参数化的执行测试用例。JUnit38ClassRunner是为了向后兼容JUnit3而定义的运行器。你也可以继承上面的类来实现新的运行器和注解功能。下面我们主要对Parameterized及Suite Runner进行介绍。
1. Parameterized Runner 实现参数化测试
为了保证单元测试的严谨性,我们模拟了不同的测试数据来测试方法的处理能力,为此我们编写了大量的单元测试方法。这些测试方法都是大同小异:代码结构都是相同的,不同的仅仅是测试数据和期望值,为了解决这个问题,JUnit4提供了参数化测试。
举个简单的例子,比如我们需要对Calculator的加法功能进行测试,我们需要考虑“正数+正数”、“正数+负数”、“负数+负数”等等情况,因此我们可能需要像下面的代码那样写多个自己的测试方法:
package com.rigel.ut;
import org.junit.Assert;
import org.junit.Test;
public class AddTest {
private Calculator calc = new Calculator();
@Test
public void testAdd1() {
calc.add(1,4);
int result = 5;
Assert.assertEquals(result, calc.getResult());
}
@Test
public void testAdd2() {
calc.add(-1,-4);
int result = -5;
Assert.assertEquals(result, calc.getResult());
}
// any other test methods
// ...
}
package com.rigel.ut;
import java.util.Arrays;
import java.util.Collection;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class AddTest {
private int result;
private int param1;
private int param2;
/**
* 存放需要进行测试的数据集合
* */
@Parameters
@SuppressWarnings("rawtypes")
public static Collection prepareData() {
Object[][] data = {{5, 1, 4}, {-3, 1, -4}, {-5, -1, -4}};
return Arrays.asList(data);
}
/**
* 对变量进行初始化
* */
public AddTest(int result, int param1, int param2) {
this.result = result;
this.param1 = param1;
this.param2 = param2;
}
@Test
public void addTest1() {
Calculator calc = new Calculator();
calc.add(param1, param2);
Assert.assertEquals(result, calc.getResult());
}
}
代码的实现过程大致如下。首先,因为是采用了参数化测试,这时候TestCase所采用的Runner已经不再是系统默认的Runner,所
以,在类的定义处,必须通过@RunWith注解指定该TestCase采用的是Parameterized.class
Runner;其次,因为需要设定你的测试数据集合,主要是通过@parameters注解标注的 prepareData
方法来实现的,其返回的便是一个包含测试数据及测试结果的数据集合;最后需要定义该测试类的参数初始化过程,即需要制定其定义的参数是如何与设定的测试数
据集合中的数据对应上。在此基础上,便可以利用定义的参数来写你的测试方法,这样只需要定义一次@Test注解的测试方法,就可以把所有的测试数据都跑一
遍了。其最后测试结果如下:
2. Suite Runner 实现打包测试
一般在一个项目中,只写一个测试类是不可能的,我们会写出很多很多个测试类。如何这些测试类必须一个一个的执行,也是非常麻烦的事情。鉴于此,JUnit为我们提供了打包测试的功能,将所有需要运行的测试类集中起来,一次性的运行完毕,这大大的方便了我们的测试工作。
为
了方便说明,我们分开在两个测试类 AddTest &
SubstractTest中,分别测试Calculator的加法及减法功能,然后使用Suite
Runner实现一次Run两个TestCase。其中AddTest源代码同上,而SubstractTest代码如下:
package com.rigel.ut;
import org.junit.Assert;
import org.junit.Test;
public class SubstractTest extends SupperTest{
private Calculator calc = new Calculator();
@Test
public void testSubstract() {
int result = 5;
calc.substract(10, 5);
Assert.assertEquals(result, calc.getResult());
}
}
实现打包测试的TestCase取名“SuiteTest”,其代码实现如下:
package com.rigel.ut;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
@RunWith(Suite.class)
@Suite.SuiteClasses({AddTest.class, SubstractTest.class})
public class SuiteTest {
}
从“SuiteTest.java”源代码可以看出,该测试类没有任何实现,其只是通过@RunWith注解标识测试采用的Runner为
Suite
Runner,另外通过@Suite.SuiteClasses注解将所有需要进行测试的类打包进来。最后SuiteTest的测试结果如下:
从图中可以很清晰的看到打包测试的具体情况。总结一下,本章节主要对JUnit4的Runner特性进行了简单的介绍,重点介绍了其中的参数化测试与打包测试的实现。本章节到此结束。谢谢。
PS: 三篇文档并非原创,算是自己看了些文档然后进行的整合吧,只是希望能把JUnit4的一些常见用法讲清楚,希望对大家有所帮助