JUnit 4

简介

JUnit是一个Java语言的单元测试框架,是由XP和TDD的创始人、软件大师Kent Back以及Eclipse架构师之一、设计模式之父Erich Gamma 编写的一个回归测试框架(regression testing framework)。
Junit测试是程序员测试,即所谓白盒测试,因为程序员知道被测试的软件如何(How)完成功能和完成什么样(What)的功能。

JUnit is a simple framework to write repeatable tests. It is an instance of the xUnit architecture for unit testing frameworks.
JUnit是一个用于编写可复用测试集的简单框架, 它是xUnit架构的单元测试框架之一。

单元测试指的是测试一个工作单元(a unit of work)的行为。
举例来说,对于建筑桥墩而言,一个螺丝钉、一根钢筋、一条钢索甚至一公斤的水泥等,都可谓是一个工作单元,验证这些工作单元行为或功能 (硬度、张力等)是否符合预期,方可确保最后桥墩安全无虑。
测试一个单元,基本上要与其它的单元独立,就软体测试而言,或支持面向对象的程序而言,例如Java,「通常」单元测试指的是测试某个方法,你给予该方法某些输入,预期该方法会产生某种输出,例如传回预期的值、产生预期的档案、新增预期的资料等。

单元测试主要是用来判断程序的执行结果与自己期望的结果是否一致。

JUnit 4 初体验

pom.xml

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency> 

项目结构如下:

这里写图片描述

ByteUtilsTest.java

package com.demo.junit;

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;

import static org.junit.Assert.*;

public class ByteUtilsTest {

    private static byte[] bytes;

    @BeforeClass
    public static void setUpClass() {
        bytes = new byte[]{1,15,127,0};
        System.out.println("class level init invoked");
    }

    @AfterClass
    public static void tearDownClass() {
        System.out.println("class level destroy invoked");
    }

    @Before
    public void setUp() {
        System.out.println("method level init invoked");
    }

    @After
    public void tearDown() {
        System.out.println("method level destroy invoked");
    }

    @Test
    public void testBytesToHexString() {
        System.out.println("...... invoke Method testBytesToHexString : ");
        String actual = ByteUtils.bytesToHexString(bytes);
        String expected = "010F7F00";
        assertEquals(expected, actual);
    }

    @Test(expected = NullPointerException.class)
    public void testBytesToHexStringNull() {
        System.out.println("...... invoke Method testBytesToHexStringNull ");
        ByteUtils.bytesToHexString(null);
    }

    @Test(timeout = 100)
    public void testTimeout1() throws InterruptedException {
        System.out.println("...... invoke Method testTimout1 ");
        Thread.sleep(200);
    }

    @Test(timeout = 100)
    public void testTimeout2() {
        System.out.println("...... invoke Method testTimout2 ");
    }

    @Test
    @Ignore("......(暂时忽略测试说明)")
    public void testIgnore() {
        System.out.println("...... invoke Method testIgnore ");
    }
}

表一 JUnit 注解

注解描述
@Test public void method() {}@Test代表需要运行单元测试的方法
@Test(expected = NullPointerException.class) public void method() {}expected代表测试方法期望抛出指定的异常,如果运行测试并没有抛出这个异常,则 JUnit 会认为这个测试没有通过;这为验证被测试方法在错误的情况下是否会抛出预定的异常提供了便利。
@Test(timeout = 100) public void method() {}timeout指定被测试方法被允许运行的最长时间应该是多少,如果测试方法运行时间超过了指定的毫秒数,则 JUnit 认为测试失败;这个参数对于性能测试有一定的帮助。
@Test @Ignore("......") public void method() {}@Ignore用于暂时忽略某个测试方法,因为有时候由于测试环境受限,并不能保证每一个测试方法都能正确运行,它迫使该框架忽略掉一个特别的测试方法;也可以传入一条消息来向恰巧进行这项忽略测试的可信的开发人员传达您的决定。注解可用于修饰测试类与测试方法,当修饰测试类时,表示忽略掉类中的所有测试方法;当修饰测试方法时,表示忽略掉该测试方法
@Before public void method() {}@Before用于初始化 Fixture 的方法。在每一个@Test方法运行之前都会被运行
@After public void method() {}@After用于注销 Fixture 的方法。在每一个@Test方法运行之后都会被运行
@BeforeClass public static void method() {}@BeforeClass用于初始化 Fixture 的方法。针对整个单元测试类,只会被运行一次,在所有方法运行之前被运行,可以用来初始化环境
@AfterClass public static void method() {}@AfterClass用于注销 Fixture 的方法。针对整个单元测试类,只会被运行一次,在所有方法运行之后被运行,可以用来释放资源

何谓 Fixture ?
它是指在执行一个或者多个测试方法时需要的一系列公共资源或者数据,例如测试环境,测试数据等等。在编写单元测试的过程中,您会发现在大部分的测试方法在进行真正的测试之前都需要做大量的铺垫——为设计准备 Fixture 而忙碌。这些铺垫过程占据的代码往往比真正测试的代码多得多,而且这个比率随着测试的复杂程度的增加而递增。当多个测试方法都需要做同样的铺垫时,重复代码的“坏味道”便在测试代码中弥漫开来。这股“坏味道”会弄脏您的代码,还会因为疏忽造成错误,应该使用一些手段来根除它。JUnit 专门提供了设置公共 Fixture 的方法,同一测试类中的所有测试方法都可以共用它来初始化 Fixture 和注销 Fixture。有四种 Fixture 注释:针对类层次的 Fixture 有两种,针对方法层次的 Fixture 有两种。在类层次,有 @BeforeClass 和 @AfterClass,在方法(或测试)层次,有@Before 和 @After

方法层次:在每一个测试方法执行之前,JUnit 会保证 init 方法已经提前初始化测试环境,而当此测试方法执行完毕之后,JUnit 又会调用 destroy 方法注销测试环境。注意是每一个测试方法的执行都会触发对公共 Fixture 的设置,也就是说使用注解 Before 或者 After 修饰的公共 Fixture 设置方法是方法级别的(图 5)。这样便可以保证各个独立的测试之间互不干扰,以免其它测试代码修改测试环境或者测试数据影响到其它测试代码的准确性。
可是,方法层次 Fixture 设置方式还是引来了批评,因为它效率低下,特别是在设置 Fixture 非常耗时的情况下(例如设置数据库链接)。而且对于不会发生变化的测试环境或者测试数据来说,是不会影响到测试方法的执行结果的,也就没有必要针对每一个测试方法重新设置一次 Fixture。因此在 JUnit 4 中引入了类层次的 Fixture 设置方法。

参数化测试

CalculaterParammeterizedTest.java

package com.demo.junit;

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 CalculaterParammeterizedTest {

    @Parameters
    public static Collection<Object[]> data () {
        return Arrays.asList( new Object[][] {  {1, 2, 3} , 
                                                {null, null, 0}, 
                                                {null, 1, 1}, 
                                                {100, null, 100}
                                              });
    }

    private Integer input1;
    private Integer input2;
    private Integer expected;

    public CalculaterParammeterizedTest(Integer input1, Integer input2, Integer expected) {
        this.input1 = input1;
        this.input2 = input2;
        this.expected = expected;
    }

    @Test
    public void testAdd() {
        Assert.assertEquals(this.expected, Calculater.add(this.input1, this.input2));
    }

}

在 JUnit 4 中创建参数测试只需要五个步骤:
1. 为准备使用参数化测试的测试类指定特殊的运行器 org.junit.runners.Parameterized。
2. 为测试类声明几个变量,分别用于存放期望值和测试所用数据。
3. 为测试类声明一个使用注解 org.junit.runners.Parameterized.Parameters 修饰的,返回值为 java.util.Collection 的公共静态方法,并在此方法中初始化所有需要测试的参数对。
4. 为测试类声明一个带有参数的公共构造函数,并在其中为第二个环节中声明的几个变量赋值。
5. 编写测试方法,使用定义的变量作为参数进行测试。

为了保证单元测试的严谨性,我们模拟了不同类型的字符串来测试方法的处理能力,为此我们编写大量的单元测试方法。可是这些测试方法都是大同小异:代码结构都是相同的,不同的仅仅是测试数据和期望值。有没有更好的方法将测试方法中相同的代码结构提取出来,提高代码的重用度,减少复制粘贴代码的烦恼?现在您可以使用 JUnit 提供的参数化测试方式应对这个问题

套件测试

TestSuite.java

package com.demo.junit;

import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;

/**
 * 套件测试
 */
@RunWith(Suite.class)
@SuiteClasses({
    ByteUtilsTest.class,
    CalculaterParammeterizedTest.class
    })
public class TestSuite {

}

测试套件的写法非常简单,您只需要遵循以下规则:
1. 创建一个空类作为测试套件的入口。
2. 使用注解 org.junit.runner.RunWith 和 org.junit.runners.Suite.SuiteClasses 修饰这个空类。
3. 将 org.junit.runners.Suite 作为参数传入注解 RunWith,以提示 JUnit 为此类使用套件运行器执行。
4. 将需要放入此测试套件的测试类组成数组作为注解 SuiteClasses 的参数。
5. 保证这个空类使用 public 修饰,而且存在公开的不带有任何参数的构造函数。

在实际项目中,随着项目进度的开展,单元测试类会越来越多,可是直到现在我们还只会一个一个的单独运行测试类,这在实际项目实践中肯定是不可行的。为了解决这个问题,JUnit 提供了一种批量运行测试类的方法,叫做测试套件。这样,每次需要验证系统功能正确性时,只执行一个或几个测试套件便可以了。
套件机制用于将测试从逻辑上分组并将这些测试作为一个单个单元来运行。

后记

JUnit 的最佳实践:
1. 测试任何可能的错误。单元测试不是用来证明您是对的,而是为了证明您没有错。
2. 单元测试代码和被测试代码使用一样的包,不同的目录。
3. 测试之前是什么状态,测试执行完毕后就应该是什么状态,而不应该由于测试执行的原因导致状态发生了变化。

单元测试的范围要全面,比如对边界值、正常值、错误值得测试;对代码可能出现的问题要全面预测,而这也正是需求分析、详细设计环节中要考虑的。

JUnit中要区分错误(error)与失败(failure)
1. 错误指的是代码中抛出了异常等影响代码正常执行的情况,比如抛出了ArrayIndexOutOfBoundsException,这就叫做错误。
2. 失败指的是我们断言所期待的结果与程序实际执行的结果不一致,或者是直接调用了fail()方法,这叫做失败。

Junit的口号:keep the bar green to keep the code clean。

参考

JUnit 官网
深入探索 JUnit 4
单元测试利器 JUnit 4
使用 JUnit 进行单元测试 - 教程
JUnit+JMockit单元测试

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值