`

JUnit 深入

阅读更多
当然,JUnit 提供的功能决不仅仅如此简单,在接下来的内容中,我们会看到 JUnit 中很多有用的特性,掌握它们对您灵活的编写单元测试代码非常有帮助。

Fixture
何谓 Fixture ?它是指在执行一个或者多个测试方法时需要的一系列公共资源或者数据,例如测试环境,测试数据等等。在编写单元测试的过程中,您会发现在大部分的测试方法在进行真正的测试之前都需要做大量的铺垫——为设计准备 Fixture 而忙碌。这些铺垫过程占据的代码往往比真正测试的代码多得多,而且这个比率随着测试的复杂程度的增加而递增。当多个测试方法都需要做同样的铺垫时,重复代码的“坏味道”便在测试代码中弥漫开来。这股“坏味道”会弄脏您的代码,还会因为疏忽造成错误,应该使用一些手段来根除它。

JUnit 专门提供了设置公共 Fixture 的方法,同一测试类中的所有测试方法都可以共用它来初始化 Fixture 和注销 Fixture。和编写 JUnit 测试方法一样,公共 Fixture 的设置也很简单,您只需要:

1.使用注解 org,junit.Before 修饰用于初始化 Fixture 的方法。
2.使用注解 org.junit.After 修饰用于注销 Fixture 的方法。
3.保证这两种方法都使用 public void 修饰,而且不能带有任何参数。
遵循上面的三条原则,编写出的代码大体是这个样子:

// 初始化 Fixture 方法
@Before public void init(){ …… }

// 注销 Fixture 方法
@After public void destroy(){ …… }



这样,在每一个测试方法执行之前,JUnit 会保证 init 方法已经提前初始化测试环境,而当此测试方法执行完毕之后,JUnit 又会调用 destroy 方法注销测试环境。注意是每一个测试方法的执行都会触发对公共 Fixture 的设置,也就是说使用注解 Before 或者 After 修饰的公共 Fixture 设置方法是方法级别的(图 5)。这样便可以保证各个独立的测试之间互不干扰,以免其它测试代码修改测试环境或者测试数据影响到其它测试代码的准确性。


图 5 方法级别 Fixture 执行示意图


可是,这种 Fixture 设置方式还是引来了批评,因为它效率低下,特别是在设置 Fixture 非常耗时的情况下(例如设置数据库链接)。而且对于不会发生变化的测试环境或者测试数据来说,是不会影响到测试方法的执行结果的,也就没有必要针对每一个测试方法重新设置一次 Fixture。因此在 JUnit 4 中引入了类级别的 Fixture 设置方法,编写规范如下:

1.使用注解 org,junit.BeforeClass 修饰用于初始化 Fixture 的方法。
2.使用注解 org.junit.AfterClass 修饰用于注销 Fixture 的方法。
3.保证这两种方法都使用 public static void 修饰,而且不能带有任何参数。
类级别的 Fixture 仅会在测试类中所有测试方法执行之前执行初始化,并在全部测试方法测试完毕之后执行注销方法(图 6)。代码范本如下:

// 类级别 Fixture 初始化方法
@BeforeClass public static void dbInit(){ …… }

// 类级别 Fixture 注销方法
@AfterClass public static void dbClose(){ …… }



图 6 类级别 Fixture 执行示意图


异常以及时间测试

注解 org.junit.Test 中有两个非常有用的参数:expected 和 timeout。参数 expected 代表测试方法期望抛出指定的异常,如果运行测试并没有抛出这个异常,则 JUnit 会认为这个测试没有通过。这为验证被测试方法在错误的情况下是否会抛出预定的异常提供了便利。举例来说,方法 supportDBChecker 用于检查用户使用的数据库版本是否在系统的支持的范围之内,如果用户使用了不被支持的数据库版本,则会抛出运行时异常 UnsupportedDBVersionException。测试方法 supportDBChecker 在数据库版本不支持时是否会抛出指定异常的单元测试方法大体如下:
 @Test(expected=UnsupportedDBVersionException.class) 
	 public void unsupportedDBCheck(){ 
		……
 } 
 


注解 org.junit.Test 的另一个参数 timeout,指定被测试方法被允许运行的最长时间应该是多少,如果测试方法运行时间超过了指定的毫秒数,则 JUnit 认为测试失败。这个参数对于性能测试有一定的帮助。例如,如果解析一份自定义的 XML 文档花费了多于 1 秒的时间,就需要重新考虑 XML 结构的设计,那单元测试方法可以这样来写:
 @Test(timeout=1000) 
	 public void selfXMLReader(){ 
		……
 } 
 
忽略测试方法

JUnit 提供注解 org.junit.Ignore 用于暂时忽略某个测试方法,因为有时候由于测试环境受限,并不能保证每一个测试方法都能正确运行。例如下面的代码便表示由于没有了数据库链接,提示 JUnit 忽略测试方法 unsupportedDBCheck:

 @ Ignore(“db is down”) 
 @Test(expected=UnsupportedDBVersionException.class) 
	 public void unsupportedDBCheck(){ 
		……
 } 
 


但是一定要小心。注解 org.junit.Ignore 只能用于暂时的忽略测试,如果需要永远忽略这些测试,一定要确认被测试代码不再需要这些测试方法,以免忽略必要的测试点。

测试运行器

又一个新概念出现了——测试运行器,JUnit 中所有的测试方法都是由它负责执行的。JUnit 为单元测试提供了默认的测试运行器,但 JUnit 并没有限制您必须使用默认的运行器。相反,您不仅可以定制自己的运行器(所有的运行器都继承自 org.junit.runner.Runner),而且还可以为每一个测试类指定使用某个具体的运行器。指定方法也很简单,使用注解 org.junit.runner.RunWith 在测试类上显式的声明要使用的运行器即可:
 @RunWith(CustomTestRunner.class) 
 public class TestWordDealUtil { 
……
 } 
 




显而易见,如果测试类没有显式的声明使用哪一个测试运行器,JUnit 会启动默认的测试运行器执行测试类(比如上面提及的单元测试代码)。一般情况下,默认测试运行器可以应对绝大多数的单元测试要求;当使用 JUnit 提供的一些高级特性(例如即将介绍的两个特性)或者针对特殊需求定制 JUnit 测试方式时,显式的声明测试运行器就必不可少了。

测试套件

在实际项目中,随着项目进度的开展,单元测试类会越来越多,可是直到现在我们还只会一个一个的单独运行测试类,这在实际项目实践中肯定是不可行的。为了解决这个问题,JUnit 提供了一种批量运行测试类的方法,叫做测试套件。这样,每次需要验证系统功能正确性时,只执行一个或几个测试套件便可以了。测试套件的写法非常简单,您只需要遵循以下规则:

1.创建一个空类作为测试套件的入口。
2.使用注解 org.junit.runner.RunWith 和 org.junit.runners.Suite.SuiteClasses 修饰这个空类。
3.将 org.junit.runners.Suite 作为参数传入注解 RunWith,以提示 JUnit 为此类使用套件运行器执行。
4.将需要放入此测试套件的测试类组成数组作为注解 SuiteClasses 的参数。
5.保证这个空类使用 public 修饰,而且存在公开的不带有任何参数的构造函数。
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.为准备使用参数化测试的测试类指定特殊的运行器 org.junit.runners.Parameterized。
2.为测试类声明几个变量,分别用于存放期望值和测试所用数据。
3.为测试类声明一个使用注解 org.junit.runners.Parameterized.Parameters 修饰的,返回值为 java.util.Collection 的公共静态方法,并在此方法中初始化所有需要测试的参数对。
4.为测试类声明一个带有参数的公共构造函数,并在其中为第二个环节中声明的几个变量赋值。
5.编写测试方法,使用定义的变量作为参数进行测试。
我们按照这个标准,重新改造一番我们的单元测试代码:

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 中添加相应的数组即可,不再需要复制粘贴出一个新的方法出来了。

分享到:
评论

相关推荐

    junit深入

    博文链接:https://jamesfork.iteye.com/blog/126890

    JUnit深入浅出

    《JUnit深入浅出》 JUnit,作为Java编程语言中最受欢迎的单元测试框架,是软件开发过程中不可或缺的一部分。本文将深入探讨JUnit的核心概念、使用方法以及它在实际开发中的重要性。 首先,我们要理解什么是单元...

    Junit深入理解和学习的必备资料

    深入理解和学习JUnit对于任何Java开发者来说都至关重要,因为它有助于确保代码的质量、可维护性和可靠性。以下是对JUnit的一些关键知识点的详细说明: 1. **JUnit基础知识**:JUnit是一个基于Java的开源测试框架,...

    android之Junit 深入研究代码

    本文将深入探讨Android中的Junit使用,结合提供的代码示例,帮助开发者更好地理解和实践单元测试。 一、Junit简介 Junit是一个开源的Java测试框架,它提供了一套简洁的API,使得编写和运行单元测试变得简单。在...

    android之Junit 深入研究

    本文将深入探讨JUnit在Android中的应用,以及如何在Android环境中实现测试驱动开发。 首先,我们要理解JUnit的基本概念。JUnit是一个开源的测试框架,它支持开发者编写和运行针对Java代码的单元测试。它的核心功能...

    junit5.jar

    《深入理解JUnit 5:Java单元测试框架的革新》 JUnit 5,作为Java开发者最常用的单元测试框架,是JUnit系列的最新版本,为Java测试带来了显著的改进和创新。这个名为"junit5.jar"的文件正是JUnit 5的核心库,它包含...

    JUNIT介绍JUNIT介绍

    为了深入学习和实践JUNIT 和TDD,可以参考提供的在线资源,包括如何在Eclipse中使用JUNIT、如何编写单元测试以及关于TDD的理论和实践文章。这些资料可以帮助开发者全面了解和掌握JUNIT 的使用,以及如何在实际项目中...

    Junit5.zip

    而`junit5-master.zip`可能包含了JUnit5的源代码,这对于开发者深入理解其内部机制和实现原理非常有帮助。 `junit5-r5.4.0.zip`可能是JUnit5的一个特定版本,版本号为5.4.0,它提供了一个稳定的API和修复了一些已知...

    JUnit in action JUnit Recipies

    《JUnit in Action》和《JUnit Recipes》是两本关于Java单元测试的重要书籍,它们深入浅出地介绍了如何使用JUnit框架进行高效、可靠的测试。JUnit是一个流行的开源测试框架,广泛用于Java应用程序的单元测试,它提供...

    探索JUnit4扩展:深入Rule

    《探索JUnit4扩展:深入Rule》 JUnit是Java开发者最常用的单元测试框架,它极大地简化了测试代码的编写。在JUnit4中,引入了一个强大的特性——Rule,这使得测试更加灵活且可定制化。本文将深入探讨Rule的概念、...

    JUnit入门,深入浅出,实例为证

    JUnit入门简介,初学JUnit适用,深入浅出,实例为证,

    junit测试_java_JUnit_JUnit测试_

    JUnit是Java编程语言中最常用的单元测试框架之一,它允许开发者编写可执行的测试用例来验证代码的功能。...通过对"junit测试.txt"的深入学习,你可以掌握有效的单元测试技巧,提升你的代码质量和开发效率。

    Junit4.4 Junit3.8.1

    JUnit是Java编程语言中最常用的单元测试框架之一,它允许开发者编写可执行的测试用例来验证代码的功能...通过提供的`.zip`和`.chm`文件,学习者可以深入研究这两个版本的源码,进一步提升对单元测试的理解和实践能力。

    junit-4.12.jar下载

    同样,`junit-4.11-sources.jar`和`junit-4.7-src.jar`包含了JUnit源代码,这有助于开发者深入理解内部实现,甚至可以自定义或扩展JUnit的功能。 除了基本的断言和测试方法,JUnit 4.12还包括了诸如注解驱动的测试...

    Junit4.12和依赖包

    在本文中,我们将深入探讨Junit4.12的核心概念、功能以及如何在项目中引入和使用。 首先,Junit4.12引入了注解(Annotation)的概念,这是它的一大亮点。注解允许开发者直接在测试方法上标记元数据,如`@Test`表示...

    JUnit讲课文档JUnit讲课文档

    这篇文档将深入讲解JUnit的核心机制和使用方法。 JUnit的内部机制首先从测试注解开始,如@Test,它是标识一个方法作为测试用例的关键。当运行测试时,JUnit会自动寻找这些注解并执行对应的方法。除此之外,还有诸如...

Global site tag (gtag.js) - Google Analytics