发布于 2026-01-06 4 阅读
0

如何在 Java 中开始单元测试:JUnit 5 完全入门

如何在 Java 中开始单元测试:JUnit 5 完全入门

大家好,Dev.to 的朋友们!在这篇文章中,我想重点介绍使用 JUnit5 库进行 Java 单元测试。与之前的 JUnit 4 版本相比,JUnit5 引入了一些值得关注的新特性和测试方法。我们将概述什么是单元测试以及为什么要进行单元测试;如何在项目中安装 JUnit 5;什么是基本的测试结构;如何使用断言 API 以及如何将多个测试组合成一个测试套件。

顺便说一下,原文发布在我的博客上,你可以在这里找到它。

什么是单元测试?

单元测试是软件测试的一个级别,它将软件的各个组件隔离出来进行测试。例如,我们有一个UserService 组件。它可能关联着各种依赖项,例如用于连接数据源的UserDAO 组件,或者用于发送确认邮件的EmailProvider 组件。但在单元测试中,我们将UserService 组件隔离出来,并可能模拟其关联的依赖项(如何进行模拟,我们将在下一章中讨论)。

单元测试给我们带来了诸多好处,仅举几例:

  • 它能增强我们修改代码时的信心。如果单元测试编写良好,并且每次代码更改后都运行它们,那么在引入新功能时,我们就能发现任何错误。
  • 它起到文档的作用。当然,代码文档化包含多种工具,单元测试就是其中之一——它向其他开发人员描述了代码的预期行为。
  • 它能提高代码的可重用性,因为好的单元测试需要代码组件模块化。

这些只是单元测试众多优势中的一小部分。现在,我们已经定义了什么是单元测试以及为什么要使用它,接下来就可以开始学习 JUnit 5 了。

安装 JUnit5

构建工具支持

要获得对 JUnit5 的原生支持,您应该拥有 Gradle 4.6+ 或 Maven 2.22.0+ 版本。

对于 Maven,您需要在pom.xml文件中添加:

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <version>{$version}</version>
    <scope>test</scope>
</dependency>
Enter fullscreen mode Exit fullscreen mode

对于 Gradle,请添加到build.gradle 文件中:

testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '$version'
Enter fullscreen mode Exit fullscreen mode

您可以在官方仓库中找到最新版本的 JUnit5 。另外,如果您要将 JUnit 5 与 Vert.x 框架一起使用,可以使用Vert.x JUnit5 扩展

IDE 支持

Intelij IDEA 自 2016.2 版本起原生支持 JUnit5,Eclipse 自 4.7.1a 版本起也支持 JUnit5。

单元测试剖析

基本结构

考虑以下示例:我们有一个程序,用于在整数数组中执行线性搜索。以下是主类,我们将其放在src/main/java/文件夹中:

class LinearSearcher(){

    private int[] data;

   LinearSearcher(int[] arr){
      this.data = arr;
   }

   int getPositionOfNumber(int value){
      int n = data.length; 
      for(int p = 0; i < n; i++) 
         { 
           if(data[p] == value) 
            return p; 
          } 
      return -1; 
   }
}

Enter fullscreen mode Exit fullscreen mode

然后将以下第二段代码添加到src/test/java文件夹中:

class LinearSearcherTest{

    private static LinearSearcher searcher;

    //before
    @BeforeAll
    static void setup(){
       int[] array = {2, 3, 4, 10, 40};
       searcher = new LinearSearcher(array);
    }

    //Actual test methods
    @Test
    void getPosition(){
       int result = searcher.getPositionOfNumber(10);
       Assertions.assertEquals(3,result);
    }

    @Test
    void noSuchNumber(){
       int result = searcher.getPositionOfNumber(55);
       Assertions.assertEquals(-1, result);
    }

    //after
    @AfterAll
    static void finish(){
       System.out.println("Tests are finished!");
    }
}
Enter fullscreen mode Exit fullscreen mode

让我们来看看这段代码做了什么。我们引入了一个新的类LinearSearcher ,它有一个方法getPostionOfNumber,该方法返回数组中值的位置,如果数组中不存在该值,则返回 -1。

在第二类LinearSearcherTest中,我们实际进行单元测试。我们预期会出现两种情况:当数组中存在一个数字(在本例中为 10)时,我们期望返回该数字的位置(例如 3)。如果数组中不存在该数字(例如 55),则搜索器应返回 -1。现在,您可以运行这段代码并检查结果。

方法之前

你会注意到有两个方法分别用@BeforeAll@AfterAll注解。它们的作用是什么?第一个方法对应于“之前”方法。这类方法有两个:

这些方法对于设置单元测试环境(例如,创建实例)非常方便。

方法之后

就像有前置方法一样,也有后置方法。而且,后置方法还有以下几种:

  • @AfterAll -静态方法将在当前类中所有 @test方法之后执行一次。
  • @AfterEach -在当前类中每个 @test方法之后将执行的方法。

使用标准断言 API

断言 API是一系列实用方法的集合,用于支持在测试中断言条件。虽然有很多可用方法,但我们将重点介绍其中最重要的几种。

断言非空

当我们需要断言实际对象不为空时,可以使用以下方法:

assertNotNull(Object obj);
Enter fullscreen mode Exit fullscreen mode

如果对象不为空,则方法通过;否则,方法失败。

断言等于

这个组包含很多方法,所以我不会提供所有重载版本,而是重点介绍一个通用签名:

assertEquals(expected_value, actual_value, optional_message);
Enter fullscreen mode Exit fullscreen mode

这些方法有两个必需参数和一个可选参数

  • expected_value = 我们想要接收的结果
  • 实际值 = 测试值
  • optional_message = String message,如果方法失败,则会显示在 STDOUT 上。

值可以是基本类型:int、double、float、long、short、boolean、char、byte,以及字符串和对象。我们可以将以下测试方法添加到此组中:

  • assertArrayEquals - 检查预期数组和实际数组是否相等。数组均为基本类型。
  • AssertFalseAssertTrue分别检查提供的布尔条件是否为假或为真。
  • assertIterableEquals - 与 assertArrayEquals 相同,但适用于可迭代对象(例如 List、Set 等)

正如我提到的,本节中有很多重载方法,因此值得查阅官方文档以获取具体的签名。

断言抛出

这是 JUnit5 的一项创新。假设你有一个会抛出异常的方法:

Car findCarById(String id) throws FailedProviderException;
Enter fullscreen mode Exit fullscreen mode

此方法通过 ID 从底层数据库中检索单个车辆,并在数据库出现问题时抛出 FailedProviderException 异常。换句话说,我们将可能出现的数据源异常(例如 SQLException 或 NoSQL 数据库的异常)封装在一个接口中,从而实现了与具体实现的独立性。

我们如何测试是否抛出了异常?以前在 JUnit4 中,我们使用注解:

@Test(expected = FailedProviderException.class)
void exceptionThrownTest() throws Exception{
    Car result = repository.findCarById("non-existing-id");
}
Enter fullscreen mode Exit fullscreen mode

顺便一提,TestNG 也使用了相同的思路。JUnit5 引入了assertThrows方法。请看,我们会如何处理这种情况:

@Test
void exceptionThrownTest(){
    Assertions.assertThrows(FailedProviderException.class, ()->{
        Car result = repository.findCarById("non-existing-id");
    });
}
Enter fullscreen mode Exit fullscreen mode

此方法签名包含两个部分:

  1. 预期会抛出异常
  2. 包含代码片段的可执行文件的 Lambda 表达式,可能会抛出异常。

同样,正如我们前面提到的 assertEquals 方法组的方法一样,我们可以提供一个可选的字符串消息作为第三个参数。

断言超时

当我们需要断言测试在设定的超时时间内完成时,可以使用以下方法:

assertTimeout(Duration timeout, Executable executable)
Enter fullscreen mode Exit fullscreen mode

其思路与 assertThrows 方法相同,但这里我们需要指定超时时间。第二个参数是一个相同的可执行 lambda 表达式。第三个可选参数是一个字符串消息。让我们来看一个例子:

@Test
void in3secondsTest(){
   Assertions.assertTimeout(Duration.ofSeconds(3), ()->{
      //some code
   });
}
Enter fullscreen mode Exit fullscreen mode

请注意,此方法使用Duration API来指定时间范围。它提供了一些便捷的方法,例如 ofSeconds()、ofMills() 等。如果您还不熟悉 Duration API,不妨查看一下这篇教程

失败

最后,如果我们需要让测试失败怎么办?只需使用Assertions.fail()方法即可。同样,有好几种方法:

  • fail(字符串消息)= 使用给定的失败消息执行测试失败。
  • fail (String message, Throwable cause) = 使用给定的失败消息以及根本原因使测试失败。
  • 失败(可抛出原因)= 因给定的根本原因导致测试失败。

创建测试套件

如果您有多个单元测试,并且想要一次性执行它们,您可以创建一个测试套件

这种方法允许你将测试分散到多个测试类和不同的包中运行。

假设我们有测试用例 TestA、TestB 和 TestC,它们分别位于三个包中:net.mednikov.teststutorial.groupA、net.mednikov.teststutorial.groupA 和 net.mednikov.teststutorial.groupC。我们可以编写测试套件来将它们组合起来:

@RunWith(JUnitPlatform.class)
@SelectPackages({net.mednikov.teststutorial.groupA, net.mednikov.teststutorial.groupB, net.mednikov.teststutorial.groupC})
public class TestSuite(){}
Enter fullscreen mode Exit fullscreen mode

现在,您可以将此方法作为一个测试套件来运行。

参考

结论

在这篇文章中,我们学习了什么是单元测试以及为什么要进行单元测试;如何在项目中安装 JUnit 5;什么是基本的测试结构;如何使用断言 API 以及如何将来自不同包的多个测试组合到一个测试套件中。当然,JUnit 5 是一个非常庞大的主题,这篇文章只是冰山一角。一些框架,例如 Vert.x,提供了专门的 JUnit 5 扩展,例如vertx-junit5。祝您使用 JUnit 5 一切顺利!:)

文章来源:https://dev.to/iuriimednikov/how-to-start-with-unit-testing-in-java-a-complete-introduction-to-junit-5-3cc