JUnit单元测试教程(翻译自Java Code Geeks)

时间:2021-11-01 23:03:16

JUnit单元测试教程——终极指南

说明

本教程翻译自Java Code Geeks,原文网址:https://www.javacodegeeks.com/2014/11/junit-tutorial-unit-testing.html#suite_tests。翻译的过程中,有少部分内容是译者添加的解释说明和对原文章中错误地方的修正。水平有限,如果有错误的地方,希望能在我的博客里告诉我,谢谢。

相关的例子不想自己写的同学们可以去这里下载:http://download.csdn.net/detail/rainnnbow/9603847

1. 单元测试简介

1.1 什么是单元测试?

一个单元可以是一个方法、一个类、一个包、 或者一个子系统。所以术语单元测试表示对代码中的很小的单元的测试行为,以确保它们能够按预期工作。例如,我们给定一些输入,测试是否是预期的输出,或者测试一个条件是真还是假。

这种测试行为能帮助开发者发现隐藏在代码后面的错误,并提高代码质量。单元测试也被用来确保代码能够按照预期工作,以防止未来可能的变化。

1.2 测试覆盖

总的来说,开发者社区对代码测试要达到多少比例存在不同的观点。许多开发者认为代码的测试覆盖率要达到100%。另一些则认为50%或者更低就可以了。但无论如何,你都应该为你的代码中复杂的以及重要的部分编写测试。

1.3 Java中的单元测试

Java中最著名的测试框架是JUnit,本指南是专门介绍JUnit的,接下来的部分将详细介绍该测试框架的更多细节。Java中另一个著名的测试框架是TestNG。

2. JUnit简介

JUnit是一个开源的测试框架,它被用来编写和运行可重复的自动化测试,保证我们的代码按照预期的执行。JUnit被广泛使用:它可以在Java程序中单独使用(使用命令行)或者结合IDE使用,如Eclipse。

JUnit提供以下功能:

  • 断言测试的预期结果。
  • 共享测试数据的特性。
  • 使用Test suites简化测试用例的组织和运行。
  • 图形化和文本化测试用例?(Graphical and textual test runners.)

JUnit可以用来测试以下内容:

  • 一个对象
  • 部分对象——一个方法或几个相互影响的方法
  • 多个对象之间的交互

2.1 使用Eclipse实现简单JUnit测试例子

在这一节我们将展示一个简单的JUnit例子。首先来看一下我们要测试的类:

Calculate.java:

package com.javacodegeeks.junit;

public class Calculate {

    public int sum(int var1, int var2) {
        System.out.println("Adding values: " + var1 + " + " + var2);
        return var1 + var2;
    }

}

在上面的源码中,我们看到该类有一个public的方法sum(),方法接受两个int参数,相加之后返回结果。我们将测试这个方法。因此我们需要创建另一个类,并在这个类中为测试Calculate类中的所有需要测试的方法一一编写测试方法(在这个例子中,我们只有一个方法需要测试)。这是最普通的使用方法。当然如果一个方法非常复杂,我们可以为这个方法编写多个测试方法。编写测试用例的更多细节,我们将在下一节介绍。下面的CalculateTest.java类就是我们的测试类:

package com.javacodegeeks.junit;

import static org.junit.Assert.*;

import org.junit.Test;

public class CalculateTest {

    Calculate calculation = new Calculate();
    int sum = calculation.sum(2, 5);
    int testSum = 7;

    @Test
    public void testSum() {
        System.out.println("@Test sum(): " + sum + " = " + testSum);
        assertEquals(sum, testSum);
    }

}

让我们来解释一下上面的代码。首先我们看到在testSum()方法上有一个@Test注解。这个注解表示这个方法可以作为一个测试用例运行。所以testSum()方法被用来测试sum()方法。我们还看到了assertEquals(sum, testSum)方法,assertEquals ([String message], object expected, object actual)方法用来接手两个对象,并断言这两个对象是否相等。

运行该测试类,Run As–>JUnit Test,程序输出将如下所示:

JUnit单元测试教程(翻译自Java Code Geeks)

为了查看JUnit测试的实际结果,Eclipse提供了JUnit窗口来显示测试结果。在这个用例中,测试成功,JUnit窗口不会显示任何错误或失败信息,所以我们看到了如下所示的界面:

JUnit单元测试教程(翻译自Java Code Geeks)

如果我们改变一下代码:

int testSum = 10;

修改代码后两个整数的测试不再相等,程序输出将如下所示:

JUnit单元测试教程(翻译自Java Code Geeks)

同时,在JUnit窗口中会显示测试出错,并给出错误信息:

JUnit单元测试教程(翻译自Java Code Geeks)

2.2 JUnit注解

本节我们将介绍JUnit4支持的基本注解,下表是对这些索引的总结:

JUnit单元测试教程(翻译自Java Code Geeks)

让我们来看一个包含了以上注解的测试类的例子:

AnnotationsTest.java:

package com.javacodegeeks.junit;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import java.util.ArrayList;

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

public class AnnotationsTest {

    private ArrayList testList;

    /**
     * if the method isn't static, throw following exception:
     * java.lang.Exception: Method onceExecutedBeforeAll() should be static
     */
    @BeforeClass
    public static void onceExecutedBeforeAll() {
        System.out.println("@BeforeClass: onceExecutedBeforeAll");
    }

    @Before
    public void executedBeforeEach() {
        testList = new ArrayList();
        System.out.println("@Before: executedBeforeEach");
    }

    /**
     * If the method isn't static, throw following exception:
     * java.lang.Exception: Method onceExecutedAfterAll() should be static
     */
    @AfterClass
    public static void onceExecutedAfterAll() {
        System.out.println("@AfterClass: onceExecutedAfterAll");
    }

    @After
    public void executedAfterEach() {
        testList.clear();
        System.out.println("@After: executedAfterEach");
    }

    @Test
    public void EmptyCollection() {
        assertTrue(testList.isEmpty());
        System.out.println("@Test: EmptyArrayList");
    }

    @Test
    public void OneItemCollection() {
        testList.add("oneItem");
        assertEquals(1, testList.size());
        System.out.println("@Test: OneItemArrayList");
    }

    @Ignore
    public void executionIgnored() {
        System.out.println("@Ignore: This execution is ignored");
    }
}

执行以上测试,控制台输出如下所示:

JUnit单元测试教程(翻译自Java Code Geeks)

JUnit窗口显示如下信息:

JUnit单元测试教程(翻译自Java Code Geeks)

2.3 Junit断言

本节我们将展示一些断言方法。所有这些方法由Assert类提供,Assert类继承自java.lang.Object。这些断言能帮助你编写测试用例,监测到失败的测试。下表是对一些最常用的断言方法的详细介绍:

JUnit单元测试教程(翻译自Java Code Geeks)

让我们看一个上述断言的例子:

AssertionsTest.java

package com.javacodegeeks.junit;

import static org.junit.Assert.*;

import org.junit.Test;

public class AssertionsTest {

    @Test
    public void test() {

        String obj1 = "junit";
        String obj2 = "junit";
        String obj3 = "test";
        String obj4 = "test";
        String obj5 = null;
        int var1 = 1;
        int var2 = 2;
        int[] arithmetic1 = {1,2,3};
        int[] arithmetic2 = {1,2,3};

        assertEquals(obj1, obj2);
        assertSame(obj3, obj4);
        assertNotSame(obj2, obj4);
        assertNotNull(obj1);
        assertNull(obj5);
        assertTrue(var1 != var2);
        assertFalse(var1 == var2);
        assertArrayEquals(arithmetic1, arithmetic2);

    }

}

从以上类中我们可以看到断言方法是怎样工作的。

  • assertEquals():如果两个对象相等,方法正常返回;否则在JUnit窗口中将显示错误,该测试将被中断。
  • assertSame()与assertNotSame():测试两个对象的引用是否指向了同一个对象。
  • assertNull()与assertNotNull():测试一个变量是否是null或非null。
  • assertTrue()与assertFalse():测试一个条件或变量时true或false.。
  • assertArrayEquals():比较两个数组是否相等。如果相等,方法正常返回;否则在JUnit窗口中将显示错误,该测试将被中断。

3. 使用Eclipse实现完整的JUnit例子

本节我们将展示一个使用JUnit的完整例子。我们将讲解怎样创建和运行测试用例,以及怎样使用JUnit的注解和断言。

3.1 创建工程

创建一个名为JUnitGuide的java工程。创建一个包com.javacodegeeks.junit用来放被测试的类。对于测试类,一个好的实践是创建一个专用于测试的源码包。这样被测试类和测试类就被分在了不同的源码包中。创建test包:右键单击你的工程, New–>Source Folder,命名源码包为test。

JUnit单元测试教程(翻译自Java Code Geeks)

提示:创建源码包的另一种方式:右键单击你的工程PropertiesJava Build Path,选择Source 项,然后Add FolderCreate New Folder。输入源码包名,点击finish。

你能看到在工程里有两个源码包:

JUnit单元测试教程(翻译自Java Code Geeks)

你可以在test源码包中也创建一个名称为com.javacodegeeks.junit的包,这样你的测试类就不会被放在默认的包中。

3.2 创建要被测试的Java类

右击src源码包,创建一个名为FirstDayAtScholl.java的类。该类的public方法将被测试。

FirstDayAtSchool.java

JUnit单元测试教程(翻译自Java Code Geeks)

3.3 创建并运行JUnit测试用例

为已经存在的类FirstDayAtSchool.java创建测试用例:选中FirstDayAtSchool.java类,右击选择NewJUnit Test Case。改变源码包,将测试用例类放在test源码包中。确保选择“JUnit 4 test”选项。

JUnit单元测试教程(翻译自Java Code Geeks)

点击“Finish”。如果你的项目classpath下还没有包含JUnit4,将会弹出如下所示的信息让你将JUnit4 Library添加进你的工程中。

JUnit单元测试教程(翻译自Java Code Geeks)

(译者注:也可以直接在test源码包中像创建普通类一样创建一个测试用例类。只要该类中的方法用@Test注解标注上,就能引入JUnit框架,并成为测试用例。)

以下就是我们创建的名为FirstDayAtSchoolTest.java的测试用例。

FirstDayAtSchoolTest.java

JUnit单元测试教程(翻译自Java Code Geeks)

右键Run AsJUnit Test,运行测试用例。

程序输出如下所示:

JUnit单元测试教程(翻译自Java Code Geeks)

JUnit窗口显示如下:

JUnit单元测试教程(翻译自Java Code Geeks)

此时JUnit窗口没有任何错误或失败。如果我们改变其中一个数组,让它包含比预期更多的元素:

JUnit单元测试教程(翻译自Java Code Geeks)

再次运行测试类,JUnit窗口将显示有一个失败的测试用例:

JUnit单元测试教程(翻译自Java Code Geeks)

或者,我们再次改变数组,让它包含与预期不同的元素:

JUnit单元测试教程(翻译自Java Code Geeks)

再次运行测试类,JUnit窗口将再次显示一个测试用例fail:

JUnit单元测试教程(翻译自Java Code Geeks)

3.4 使用@Ignore注解

在上面的例子中,让我们看看怎样使用@Ignore注解。在测试类FirstDayAtSchoolTest.java的testAddPencils()方法上标注@Ignore注解。通过这种方式,我们将该测试方法忽略掉,测试时不会执行。

JUnit单元测试教程(翻译自Java Code Geeks)

执行测试,输出如下所示:@Ignore注解的测试方法没有执行。

JUnit单元测试教程(翻译自Java Code Geeks)

JUnit窗口显示如下:

JUnit单元测试教程(翻译自Java Code Geeks)

现在,我们去掉testAddPencils()方法上的@Ignore注解,将@Ignore注解标注在FirstDayAtSchoolTest.java类上。

JUnit单元测试教程(翻译自Java Code Geeks)

运行该测试类发现:整个测试用例将不会执行,所以控制台上没有任何输出。JUnit窗口显示如下信息:

JUnit单元测试教程(翻译自Java Code Geeks)

3.5 创建测试套件(suite tests)

本节,我们将展示如何创建测试套件。测试套件是来自于不同类的多个测试用例的集合。使用@Runwith和@Suite注解可以让这些测试类一起运行。当你有许多测试类并且你希望这些测试类一起执行时,这是非常有用的。

在上一节类的基础上,我们可以创建两个测试类。一个测试prepareMyBag()方法,另一个测试addPencils()方法。两个测试类如下所示:

PrepareMyBagTest.java.

JUnit单元测试教程(翻译自Java Code Geeks)

AddPencilsTest.java.

JUnit单元测试教程(翻译自Java Code Geeks)

我们来创建一个测试套件类来运行以上两个测试类。测试套件类SuiteTest.java如下所示:

SuiteTest.java.

JUnit单元测试教程(翻译自Java Code Geeks)

使用@Suite.SuiteClasses注解来定义哪些测试类将被包含进测试套件一起执行。

运行该测试套件,这两个测试类将按照在@Suite.SuiteClasses注解中声明的顺序执行。

执行完毕后JUnit窗口显示信息如下:

JUnit单元测试教程(翻译自Java Code Geeks)

3.6 创建参数化测试(parameterized tests)

本节我们将展示如何创建参数化的测试。我们将使用前面的2.1节中的类作为被测试类。

什么时候一个测试类才能被认为是一个参数化的测试类呢?当测试类满足下列条件的时候:

 该类使用@RunWith(Parameterized.class)标注;

@RunWith注解告诉JUnit使用该注解标明的执行器执行测试用例,而不是使用JUnit内置的默认执行器。Parameterized是JUnit中的一个执行器,该执行器将使用一组不同的输入来多次执行一个相同的测试用例。

 该类有唯一的构造函数来创建测试数据;

 该类有一个static方法用来产生并返回测试数据,该方法使用@Parameters注解标注;

 该类有一个使用@Test标注的测试用例(译者注:废话吗这不是)。

我们创建了一个新的测试类CalculateTest.java。该类遵循以上的规则。该类源码如下:

JUnit单元测试教程(翻译自Java Code Geeks)

上如代码满足以上的所有规则。addedNumbers方法使用@Parameters标注,返回数组的集合。每一个数组包含一次测试执行的I/O数据。数组中元素的个数必须和构造函数中参数的个数一致。在该例子中,一个数组包含三个元素,两个代表要被相加的数,一个代表结果。

运行该测试用例,控制台输出如下所示:

JUnit单元测试教程(翻译自Java Code Geeks)

JUnit窗口输出信息如下:

JUnit单元测试教程(翻译自Java Code Geeks)

从上述信息可以看出,该测试用例执行了4次,这正是使用@Parameters注解标注的方法中输入的测试数据的个数。

3.7 规则(Rules)

本节我们将介绍JUnit4的一个新特性,叫做规则(Rules)。Rules允许我们灵活的增加或重新定义每一个测试方法的行为。为达到这个目的,使用@Rule注解标注测试类中public的域(fields)。这些域必须是MethodRule类型的,他们标明了一个测试方法的运行或结果报告作出了哪些改变。一个测试方法可以应用多个MethodRule。MethodRule接口有许多实现,如ErrorCollector,该规则允许测试用例在发现第一次错误是可以继续运行;ExpectedException,该规则允许我们明确预期异常的类型及异常信息;TestName,该规则允许我们在测试方法中获取到当前测试方法的名称;等等其他规则。除了JUnit已经定义的规则,开发者也可以自定义规则并在测试用例中使用。

下面,我们展示一个使用已经存在的TestName规则的例子。当一个测试类开始执行时TestName被调用。

NameRuleTest.java

JUnit单元测试教程(翻译自Java Code Geeks)

如上所示:@Rule注解标注了一个public的域name,该域是MethodRule类型的。确切的说,该类是MethodRule接口的实现类MethodRule类型。然后我们就可以使用name在我们的测试方法中获取当前正在运行的测试方法的名字了。

(译者注:关于Rules的详细说明,请查看JUnit4官方文档:https://github.com/junit-team/junit4/wiki/Rules。运行里面的例子,有疑问就修改例子测试,一目了然。Rules还是有不少用处的。)

3.8 策略(Categories)

JUnit的另一个新特性是策略,这允许你将特定的测试放在一起形成一个分组(group),并可以包含或排除某些分组。例如你可以将运行慢的测试用例与运行快的测试用例分开。JUnit提供了@Category注解为一个测试用例标明策略。以下是使用该新特性的例子。该特性从JUnit4.8开始支持。

JUnit单元测试教程(翻译自Java Code Geeks)

首先,定义两个策略,FastTests和SlowTests。一个策略可以是一个类或者一个借口。

JUnit单元测试教程(翻译自Java Code Geeks)

以上的代码中,我们在A类的b()方法上使用了@Category注解,标注该方法在SlowTests策略中。所以,@Category注解不仅可以标注整个类,也可以标注某一个测试方法。

JUnit单元测试教程(翻译自Java Code Geeks)

以上的代码中,我们看到整个B类使用了@Category标注。在类上标注后,该类的所有测试方法都会自动包含进@Category中声明的策略中。我们也可以看到,一个类或者一个测试方法可以属于多个策略。

JUnit单元测试教程(翻译自Java Code Geeks)

以上代码中,我们看到有一个名为SlowTestSuite_1的测试套件。一般情况下,我们认为策略就是一种测试套件。在该套件中,我们使用@IncludeCategory注解标明哪些策略将会被执行。在该例中,属于SlowTests策略的所有方法将会被执行。因此,A类中的b()方法和B类中的c方法将被执行,他们都属于SlowTests策略。

JUnit单元测试教程(翻译自Java Code Geeks)

以上代码中,我们对测试套件做了一些修改,我们添加了一个新的注解@ExcludeCategory,该注解表明那些策略将被排除。在本例中,只有A类的b()方法会执行,因为他是唯一只属于SlowTests策略,并不属于FastTests策略的测试方法。

通过以上两个例子我们看到,A类的a()方法从未执行,因为它不属于任何策略。

5. 命令行运行JUnit测试

通过使用org.junit.runner.JUnitCore 类,可以在Eclipse之外运行测试用例。该类提供了runClasses()方法允许执行一个或多个测试类。runClasses()方法的返回类型是一个org.junit.runner.Result对象。该对象可以搜集所有测试的信息。如果有失败的测试用例,你可以使用org.junit.runner.notification.Failure对象来获取失败的测试用例的描述。

下面的流程展示了如何在Eclipse之外运行你的测试。

创建一个新类JunitRunner.java如下所示:

JunitRunner.java

JUnit单元测试教程(翻译自Java Code Geeks)

我们选择运行名为AssertionsTest的测试类。

 打开DOS窗口,并定位到JunitRunner.java所在的目录(译者注:本人的目录是**\JUnitGuide\test\com\javacodegeeks\junit)。

 编译测试类和Runner类。

javac -classpath “D:\learnTech\test\junit-4.11.jar”;”D:\learnTech\test\hamcrest-core-1.3.0.jar”; AssertionsTest.java JunitRunner.java

JUnit单元测试教程(翻译自Java Code Geeks)

和在Eclipse中一样,我们需要将JUnit的jar包包含进classpath中。运行完上述命令。在Junitrunner.java所在的目录下就生成了AssertionsTest.class 和JunitRunner.class两个class文件。如下所示:

JUnit单元测试教程(翻译自Java Code Geeks)

 在DOS窗口中,退回到test源码目录(译者注:本人的目录是**\JUnitGuide\test)。

 运行JunitRunner类。

java -classpath “D:\learnTech\test\junit-4.11.jar”;”D:\learnTech\test\hamcrest-core-1.3.0.jar”; JunitRunner

JUnit单元测试教程(翻译自Java Code Geeks)

输出结果为:

JUnit单元测试教程(翻译自Java Code Geeks)