JUnit4 使用指南三 (Runner 特性分析)
Admin
2011年5月28日
大家有没有想过这个问题:当你把测试代码提交给
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为我们定义好了一些运行器,他们的层次结构如下:
提供的一些高级特性(例如即将介绍的两个特性)或者针对特殊需求定制JUnit测试方式时,显式的声明测试运行器就必不可少了。在Junit4
中,Junit为我们定义好了一些运行器,他们的层次结构如下:
其中
ParentRunner是一个抽象类,包含了一个Runner的List,可以负责多个Runner运行。
Suite是
一个测试套,继承了ParentRunner。BlockJUnit4ClassRunner则是Juint4默认的运行器,叫成Block是因为他是按
顺序一个一个执行测试用例的,Junit4中没有提供实现多线程执行的运行器。Junit4这个运行器只是单纯继承了
BlockJUnit4ClassRunner没有对其进行更改,并且它是final类型的,不允许继续被继承。 Enclosed是实现内部类的测试类的运行器。 Parameterized则可以设置参数化的执行测试用例。JUnit38ClassRunner是为了向后兼容JUnit3而定义的运行器。你也可以继承上面的类来实现新的运行器和注解功能。下面我们主要对 Parameterized及Suite Runner进行介绍。
一个测试套,继承了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 // ... }
这些测试方法内容相同,仅仅只是测试数据有差异而已,写很多Test方法,其实是比较让人纠结的。 而JUnit4提出的“参数化测试”,可以只写一个测试函数,把这若干种情况作为参数传递进去,一次性的完成测试。代码实现如下:
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注解的测试方法,就可以把所有的测试数据都跑一
遍了。其最后测试结果如下:
以,在类的定义处,必须通过@RunWith注解指定该TestCase采用的是Parameterized.class
Runner;其次,因为需要设定你的测试数据集合,主要是通过@parameters注解标注的 prepareData
方法来实现的,其返回的便是一个包含测试数据及测试结果的数据集合;最后需要定义该测试类的参数初始化过程,即需要制定其定义的参数是如何与设定的测试数
据集合中的数据对应上。在此基础上,便可以利用定义的参数来写你的测试方法,这样只需要定义一次@Test注解的测试方法,就可以把所有的测试数据都跑一
遍了。其最后测试结果如下:
2. Suite Runner 实现打包测试
一般在一个项目中,只写一个测试类是不可能的,我们会写出很多很多个测试类。如何这些测试类必须一个一个的执行,也是非常麻烦的事情。鉴于此,JUnit为我们提供了打包测试的功能,将所有需要运行的测试类集中起来,一次性的运行完毕,这大大的方便了我们的测试工作。
为
了方便说明,我们分开在两个测试类 AddTest &
SubstractTest中,分别测试Calculator的加法及减法功能,然后使用Suite
Runner实现一次Run两个TestCase。其中AddTest源代码同上,而SubstractTest代码如下:
了方便说明,我们分开在两个测试类 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的测试结果如下:
Suite
Runner,另外通过@Suite.SuiteClasses注解将所有需要进行测试的类打包进来。最后SuiteTest的测试结果如下:
从图中可以很清晰的看到打包测试的具体情况。总结一下,本章节主要对JUnit4的Runner特性进行了简单的介绍,重点介绍了其中的参数化测试与打包测试的实现。本章节到此结束。谢谢。