何谓 Fixture ?它是指在执行一个或者多个测试方法时须要的一系列公共资源或者数据,例如测试环境,测试数据等等。在编写单元测试的过程当中,您会发如今大部分的测试方法在进行真正的测试以前都须要作大量的铺垫——为设计准备 Fixture 而忙碌。这些铺垫过程占据的代码每每比真正测试的代码多得多,并且这个比率随着测试的复杂程度的增长而递增。当多个测试方法都须要作一样的铺垫时,重复代码的“坏味道”便在测试代码中弥漫开来。这股“坏味道”会弄脏您的代码,还会由于疏忽形成错误,应该使用一些手段来根除它。html
JUnit 专门提供了设置公共 Fixture 的方法,同一测试类中的全部测试方法均可以共用它来初始化 Fixture 和注销 Fixture。和编写 JUnit 测试方法同样,公共 Fixture 的设置也很简单,您只须要:java
遵循上面的三条原则,编写出的代码大致是这个样子:数据库
1
2
3
4
5
|
// 初始化 Fixture 方法
@Before public void init(){ …… }
// 注销 Fixture 方法
@After public void destroy(){ …… }
|
这样,在每个测试方法执行以前,JUnit 会保证 init 方法已经提早初始化测试环境,而当此测试方法执行完毕以后,JUnit 又会调用 destroy 方法注销测试环境。注意是每个测试方法的执行都会触发对公共 Fixture 的设置,也就是说使用注解 Before 或者 After 修饰的公共 Fixture 设置方法是方法级别的(图 5)。这样即可以保证各个独立的测试之间互不干扰,以避免其它测试代码修改测试环境或者测试数据影响到其它测试代码的准确性。数组
但是,这种 Fixture 设置方式仍是引来了批评,由于它效率低下,特别是在设置 Fixture 很是耗时的状况下(例如设置数据库连接)。并且对于不会发生变化的测试环境或者测试数据来讲,是不会影响到测试方法的执行结果的,也就没有必要针对每个测试方法从新设置一次 Fixture。所以在 JUnit 4 中引入了类级别的 Fixture 设置方法,编写规范以下:函数
类级别的 Fixture 仅会在测试类中全部测试方法执行以前执行初始化,并在所有测试方法测试完毕以后执行注销方法(图 6)。代码范本以下:工具
1
2
3
4
5
|
// 类级别 Fixture 初始化方法
@BeforeClass public static void dbInit(){ …… }
// 类级别 Fixture 注销方法
@AfterClass public static void dbClose(){ …… }
|
注解 org.junit.Test 中有两个很是有用的参数:expected 和 timeout。参数 expected 表明测试方法指望抛出指定的异常,若是运行测试并无抛出这个异常,则 JUnit 会认为这个测试没有经过。这为验证被测试方法在错误的状况下是否会抛出预约的异常提供了便利。举例来讲,方法 supportDBChecker 用于检查用户使用的数据库版本是否在系统的支持的范围以内,若是用户使用了不被支持的数据库版本,则会抛出运行时异常 UnsupportedDBVersionException。测试方法 supportDBChecker 在数据库版本不支持时是否会抛出指定异常的单元测试方法大致以下:性能
1
2
3
4
|
@Test(expected=UnsupportedDBVersionException.class)
public void unsupportedDBCheck(){
……
}
|
注解 org.junit.Test 的另外一个参数 timeout,指定被测试方法被容许运行的最长时间应该是多少,若是测试方法运行时间超过了指定的毫秒数,则 JUnit 认为测试失败。这个参数对于性能测试有必定的帮助。例如,若是解析一份自定义的 XML 文档花费了多于 1 秒的时间,就须要从新考虑 XML 结构的设计,那单元测试方法能够这样来写:单元测试
1
2
3
4
|
@Test(timeout=1000)
public void selfXMLReader(){
……
}
|
JUnit 提供注解 org.junit.Ignore 用于暂时忽略某个测试方法,由于有时候因为测试环境受限,并不能保证每个测试方法都能正确运行。例以下面的代码便表示因为没有了数据库连接,提示 JUnit 忽略测试方法 unsupportedDBCheck:测试
1
2
3
4
5
|
@ Ignore(“db is down”)
@Test(expected=UnsupportedDBVersionException.class)
public void unsupportedDBCheck(){
……
}
|
可是必定要当心。注解 org.junit.Ignore 只能用于暂时的忽略测试,若是须要永远忽略这些测试,必定要确认被测试代码再也不须要这些测试方法,以避免忽略必要的测试点。ui
又一个新概念出现了——测试运行器,JUnit 中全部的测试方法都是由它负责执行的。JUnit 为单元测试提供了默认的测试运行器,但 JUnit 并无限制您必须使用默认的运行器。相反,您不只能够定制本身的运行器(全部的运行器都继承自 org.junit.runner.Runner),并且还能够为每个测试类指定使用某个具体的运行器。指定方法也很简单,使用注解 org.junit.runner.RunWith 在测试类上显式的声明要使用的运行器便可:
1
2
3
4
|
@RunWith(CustomTestRunner.class)
public class TestWordDealUtil {
……
}
|
显而易见,若是测试类没有显式的声明使用哪个测试运行器,JUnit 会启动默认的测试运行器执行测试类(好比上面说起的单元测试代码)。通常状况下,默认测试运行器能够应对绝大多数的单元测试要求;当使用 JUnit 提供的一些高级特性(例如即将介绍的两个特性)或者针对特殊需求定制 JUnit 测试方式时,显式的声明测试运行器就必不可少了。
在实际项目中,随着项目进度的开展,单元测试类会愈来愈多,但是直到如今咱们还只会一个一个的单独运行测试类,这在实际项目实践中确定是不可行的。为了解决这个问题,JUnit 提供了一种批量运行测试类的方法,叫作测试套件。这样,每次须要验证系统功能正确性时,只执行一个或几个测试套件即可以了。测试套件的写法很是简单,您只须要遵循如下规则:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
package com.ai92.cooljunit;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
……
/**
* 批量测试 工具包 中测试类
* @author Ai92
*/
@RunWith(Suite.class)
@Suite.SuiteClasses({TestWordDealUtil.class})
public class RunAllUtilTestsSuite {
}
|
上例代码中,咱们将前文提到的测试类 TestWordDealUtil 放入了测试套件 RunAllUtilTestsSuite 中,在 Eclipse 中运行测试套件,能够看到测试类 TestWordDealUtil 被调用执行了。测试套件中不只能够包含基本的测试类,并且能够包含其它的测试套件,这样能够很方便的分层管理不一样模块的单元测试代码。可是,您必定要保证测试套件之间没有循环包含关系,不然无尽的循环就会出如今您的面前……。
回顾一下咱们在小节“JUnit 初体验”中举的实例。为了保证单元测试的严谨性,咱们模拟了不一样类型的字符串来测试方法的处理能力,为此咱们编写大量的单元测试方法。但是这些测试方法都是大同小异:代码结构都是相同的,不一样的仅仅是测试数据和指望值。有没有更好的方法将测试方法中相同的代码结构提取出来,提升代码的重用度,减小复制粘贴代码的烦恼?在之前的 JUnit 版本上,并无好的解决方法,而如今您可使用 JUnit 提供的参数化测试方式应对这个问题。
参数化测试的编写稍微有点麻烦(固然这是相对于 JUnit 中其它特性而言):
咱们按照这个标准,从新改造一番咱们的单元测试代码:
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
36
37
38
39
40
41
42
43
44
45
46
47
|
package com.ai92.cooljunit;
import static org.junit.Assert.assertEquals;
import java.util.Arrays;
import java.util.Collection;
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 TestWordDealUtilWithParam {
private String expected;
private String target;
@Parameters
public static Collection words(){
return Arrays.asList(new Object[][]{
{"employee_info", "employeeInfo"}, // 测试通常的处理状况
{null, null}, // 测试 null 时的处理状况
{"", ""}, // 测试空字符串时的处理状况
{"employee_info", "EmployeeInfo"}, // 测试当首字母大写时的状况
{"employee_info_a", "employeeInfoA"}, // 测试当尾字母为大写时的状况
{"employee_a_info", "employeeAInfo"} // 测试多个相连字母大写时的状况
});
}
/**
* 参数化测试必须的构造函数
* @param expected 指望的测试结果,对应参数集中的第一个参数
* @param target 测试数据,对应参数集中的第二个参数
*/
public TestWordDealUtilWithParam(String expected , String target){
this.expected = expected;
this.target = target;
}
/**
* 测试将 Java 对象名称到数据库名称的转换
*/
@Test public void wordFormat4DB(){
assertEquals(expected, WordDealUtil.wordFormat4DB(target));
}
}
|
很明显,代码瘦身了。在静态方法 words 中,咱们使用二维数组来构建测试所须要的参数列表,其中每一个数组中的元素的放置顺序并无什么要求,只要和构造函数中的顺序保持一致就能够了。如今若是再增长一种测试状况,只须要在静态方法 words 中添加相应的数组便可,再也不须要复制粘贴出一个新的方法出来了。
来源:https://www.ibm.com/developerworks/cn/java/j-lo-junit4/