2

JUnit 5 单元测试教程 - 程序猿阿朗

 1 year ago
source link: https://www.cnblogs.com/niumoo/p/16902100.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

点赞再看,动力无限。 微信搜「 程序猿阿朗 」。

本文 Github.com/niumoo/JavaNotes未读代码博客 已经收录,有很多知识点和系列文章。

1202902-20221118090908739-1347042939.png

在软件开发过程中,我们通常都需要测试自己的代码运行是否正常,可能对一个函数进行简单测试,也可能是多个功能的组合测试。不管使用哪种方式,都是为了更好的测试我们的代码是否存在逻辑缺陷。测试对于软件开发是非常必要的。

JUnit 5 介绍

在 Java 中比较有名的测试工具是 JUnit ,通常我们使用 JUnit 可以对一个逻辑单元进行测试,因此也叫单元测试。多个单元测试组合测试,可以确保我们的程序符合预期。JUnit 单元测试可以在开发阶段发现问题,让我们可以提前修复代码,因此十分重要。

JUnit 5 和 JUnit

JUnit 是一个 Java 语言的开源测试框架,使用 JUnit 让我们使用注解就可以进行单元测试,很是方便。

JUnit 5 是 JUnit 的升级版本,JUnit 5 使用了 Java 8 及更高版本的 Java 语言特性,如函数编程,流式编码等,因此更加强大。JUnit 5 进行单元测试的可读性更强,编写更加容易,且可以轻松扩展。

JUnit 5 基本组件

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

1202902-20221118091004336-18396501.png

JUnit Platform

JUnit Platform 是 JUnit 的基础框架,使用 JUnit Platform 才能在 JVM 启动测试,JUnit Platform 还定义了 TestEngine 测试引擎,是JUnit 测试的基础。

JUnit Jupiter

JUnit Jupiter 提供了单元测试常见的注解以及扩展接口,想要方便的进行 JUnit 单元测试,那么 Jupiter 模块就必不可少。

JUnit Vintage

JUnit Vintage 提供了对 JUnit 3 和 JUnit 4 的测试支持。

JUnit 5 依赖

使用注解进行 JUnit 单元测试,直接引入 junit-jupiter即可。

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
  	<version>5.9.1</version>
    <scope>test</scope>
</dependency>

JUnit 5 常用注解

@Test

为一个 public void 方法添加 @Test 注释,允许我们对这个方法进行测试。

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
/** 
 * @author:https://www.wdbyte.com  
 **/
class JUnitTestIsDog {

    @Test
    public void testIsDog() {
        String name = "cat";
        Assertions.assertEquals(name, "dog");
    }
}

上面的代码中使用了 Assertions.assertEquals(name, "dog") 来判断是否 name 变量是否是 dogAssertionsJUnit 提供的断言工具,后面会详细介绍。

idea 中运行可以到的错误日志,提示预期是 dog,实际是 cat

org.opentest4j.AssertionFailedError: 
Expected :cat
Actual   :dog
<Click to see difference>

如果是符合预期的,那么运行会显示正确标志。

@Test
public void testIsDog2() {
    String name = "dog";
    Assertions.assertEquals(name, "dog");
}

testIsDog2 方法测试通过。

1202902-20221118091046345-1075701206.png

@BeforeAll

使用 @BeforeAll 可以在单元测试前初始化部分信息,@BeforeAll 只能使用在静态方法上,被注解的方法会在测试开始前运行一次

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/** 
 * @author:https://www.wdbyte.com  
 **/
class JUnitBeforeAll {

    @BeforeAll
    public static void init() {
        System.out.println("初始化,准备测试信息");
    }

    @Test
    public void testIsDog() {
        String name = "dog";
        Assertions.assertEquals(name, "dog");
        System.out.println("is dog");
    }

    @Test
    public void testIsCat() {
        String name = "cat";
        Assertions.assertEquals(name, "cat");
        System.out.println("is cat");
    }
}

这会输出:

初始化,准备测试信息
is cat
is dog

@BeforeEach

使用 @BeforeEach 注解的方法,会在每一个 @Test 注解的方法运行前运行一次。

class JUnitBeforeAll {

    @BeforeAll
    public static void init() {
        System.out.println("初始化,准备测试信息");
    }

    @BeforeEach
    public void start(){
        System.out.println("开始测试...");
    }

    @Test
    public void testIsDog() {
        String name = "dog";
        Assertions.assertEquals(name, "dog");
        System.out.println("is dog");
    }

    @Test
    public void testIsCat() {
        String name = "cat";
        Assertions.assertEquals(name, "cat");
        System.out.println("is cat");
    }
}

这会输出:

初始化,准备测试信息
开始测试...
is cat
开始测试...
is dog

@AfterAll

@AfterAll 注解只能使用在静态方法上,被注解的方法会在所有单元测试运行完毕后运行一次。

class JUnitBeforeAll {

    @BeforeAll
    public static void init() {
        System.out.println("初始化,准备测试信息");
    }

    @BeforeEach
    public void start(){
        System.out.println("开始测试...");
    }

    @Test
    public void testIsDog() {
       //...
    }

    @Test
    public void testIsCat() {
        //...
    }

    @AfterAll
    public static void close() {
        System.out.println("结束,准备退出测试");
    }
}

这会输出:

初始化,准备测试信息
开始测试...
is cat
开始测试...
is dog
结束,准备退出测试

@AfterEach

使用 @AfterEach 注解的方法,会在每一个 @Test 注解的方法运行结束前运行一次

class JUnitBeforeAll {

    @BeforeAll
    public static void init() {
        System.out.println("初始化,准备测试信息");
    }

    @BeforeEach
    public void start(){
        System.out.println("开始测试...");
    }

    @Test
    public void testIsDog() { //... }

    @Test
    public void testIsCat() { //... }

    @AfterEach
    public void end(){
        System.out.println("测试完毕...");
    }

    @AfterAll
    public static void close() {
        System.out.println("结束,准备退出测试");
    }
}

这会输出:

初始化,准备测试信息
开始测试...
is cat
测试完毕...
开始测试...
is dog
测试完毕...
结束,准备退出测试

@Disabled

@Disabled 注解的方法不在参与测试,下面对 testIsDog 方法添加了 @Disabled 注解。

class JUnitBeforeAll {

    @BeforeAll
    public static void init() {
        System.out.println("初始化,准备测试信息");
    }

    @BeforeEach
    public void start(){
        System.out.println("开始测试...");
    }

    @Disabled("由于xx原因,关闭 testIsDog 测试")
    @Test
    public void testIsDog() {
        String name = "dog";
        Assertions.assertEquals(name, "dog");
        System.out.println("is dog");
    }

    @Test
    public void testIsCat() {
        String name = "cat";
        Assertions.assertEquals(name, "cat");
        System.out.println("is cat");
    }

    @AfterEach
    public void end(){
        System.out.println("测试完毕...");
    }

    @AfterAll
    public static void close() {
        System.out.println("结束,准备退出测试");
    }
}

这会输出:

初始化,准备测试信息
开始测试...
is cat
测试完毕...

由于xx原因,关闭 testIsDog 测试
结束,准备退出测试

@DisplayName

使用 @DisplayName 注解可以自定义测试方法的显示名称,下面为两个测试方法自定义名称。

class JUnitBeforeAll {

    @BeforeAll
    public static void init() {
        System.out.println("初始化,准备测试信息");
    }

    @BeforeEach
    public void start() {
        System.out.println("开始测试...");
    }

    @DisplayName("是否是狗")
    @Disabled
    @Test
    public void testIsDog() {
        String name = "dog";
        Assertions.assertEquals(name, "dog");
        System.out.println("is dog");
    }

    @DisplayName("是否是猫")
    @Test
    public void testIsCat() {
        String name = "cat";
        Assertions.assertEquals(name, "cat");
        System.out.println("is cat");
    }

    @AfterEach
    public void end() {
        System.out.println("测试完毕...");
    }

    @AfterAll
    public static void close() {
        System.out.println("结束,准备退出测试");
    }
}

idea 中运行后,可以看到配置的中文名称。

1202902-20221118091209517-1526041973.png

@ParameterizedTest

使用注解 @ParameterizedTest 结合 @ValueSource ,可以对不用的入参进行测试。下面的示例使用 @ParameterizedTest 来开始参数化单元测试,name 属性用来定义测试名称, @ValueSource 则定义了两个测试值。

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

public class JUnitParam {

    //@Test
    @DisplayName("是否是狗")
    @ValueSource(strings = {"dog", "cat"})
    @ParameterizedTest(name = "开始测试入参 {0} ")
    public void testIsDog(String name) {
        Assertions.assertEquals(name, "dog");
    }
}

这会输出:

1202902-20221118091240355-792379586.png

@Order

在类上增加注解 @TestMethodOrder ,然后在方法上使用 @Order 指定顺序,数字越小优先级越搞,可以保证测试方法运行顺序。

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.condition.EnabledOnJre;

import static org.junit.jupiter.api.condition.JRE.JAVA_19;

@TestMethodOrder(OrderAnnotation.class)
public class JUnitOrder{

    @Test
    @DisplayName("测试是否是狗")
    @Order(2)
    public void testIsDog() {
        String name = "dog";
        Assertions.assertEquals(name, "dog");
        System.out.println("is dog");
    }

    @DisplayName("是否是猫")
    @Test
    @Order(1)
    public void testIsCat() {
        String name = "cat";
        Assertions.assertEquals(name, "cat");
        System.out.println("is cat");
    }
}

这会输出:

is cat
is dog

@EnabledOnJre(JAVA_19)

只在 JRE 19 环境运行,否则运行会输出:Disabled on JRE version: xxx.

@RepeatedTest(10)

重复测试,参数 10 可以让单元测试重复运行 10 次。

JUnit 5 常用断言

在上面的例子中,已经用到了 assertEquals 来判断结果是否符合预期,assertEquals是类 org.junit.jupiter.api.Assertions 中的一个方法;除此之外,还几乎包括了所有我们日常测试想要用到的判断方法。

下面是一些演示:

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

public class JunitAssert {

    @DisplayName("是否是狗")
    @Test
    public void testIsDog() {
        String name = "dog";
        Assertions.assertNotNull(name);
        Assertions.assertEquals(name, "dog");
        Assertions.assertNotEquals(name, "cat");
        Assertions.assertTrue("dog".equals(name));
        Assertions.assertFalse("cat".equals(name));
    }

    @DisplayName("是否是猫")
    @Test
    public void testIsCat() {
        String name = "cat";
        Assertions.assertNull(name, "name is not null");
    }

}

testIsDog 中演示了一些常用的判断方法,且都可以通过验证。在 testIsCat 方法中进行了 null 值判断,显然这里无法通过测试,会抛出自定义异常 name is not null

这会输出:

org.opentest4j.AssertionFailedError: name is not null ==> 
Expected :null
Actual   :cat
<Click to see difference>

预期是一个 null 值,实际上是一个 cat 字符串。

Maven JUnit 测试

在 Maven 中进行 JUnit 测试,可以通过命令 mvn test 开始测试,默认情况下会测试所有依赖了当前源码的 JUnit 测试用例。

准备被测 Preson类放在 src.main.java.com.wdbyte.test.junit5.

package com.wdbyte.test.junit5;

public class Person {
    public int getLuckyNumber() {
        return 7;
    }
}

编写测试类 PersonTest 放在 src.test.java.com.wdbyte.test.junit5. 这里判断获取到的幸运数字是否是 8 ,明显方法返回的是 7 ,所以这里是测试会报错。

package com.wdbyte.test.junit5;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

@DisplayName("测试 Presion")
class PersonTest {

    @DisplayName("测试幸运数字")
    @Test
    void getLuckyNumber() {
        Person person = new Person();
        Assertions.assertEquals(8, person.getLuckyNumber());
    }
}

在 pom.xml 中引入 maven junit 测试依赖插件。

<build>
     <plugins>
         <plugin>
             <artifactId>maven-surefire-plugin</artifactId>
             <version>2.22.2</version>
         </plugin>
         <plugin>
             <artifactId>maven-failsafe-plugin</artifactId>
             <version>2.22.2</version>
         </plugin>
     </plugins>
</build>

执行测试命令:mvn test

➜  junit5-jupiter-starter git:(master) ✗ mvn test
[INFO] Scanning for projects...
[INFO] ....
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.wdbyte.test.junit5.PersonTest
[ERROR] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.031 s <<< FAILURE! - in com.wdbyte.test.junit5.PersonTest
[ERROR] getLuckyNumber  Time elapsed: 0.026 s  <<< FAILURE!
org.opentest4j.AssertionFailedError: expected: <8> but was: <7>
	at com.wdbyte.test.junit5.PersonTest.getLuckyNumber(PersonTest.java:18)

[INFO]
[INFO] Results:
[INFO]
[ERROR] Failures:
[ERROR]   PersonTest.getLuckyNumber:18 expected: <8> but was: <7>
[INFO]
[ERROR] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.777 s
[INFO] Finished at: 2022-11-17T23:01:09+08:00
[INFO] ------------------------------------------------------------------------

也可以指定类进行测试:mvn -Dtest=PersonTest test

一如既往,文章中代码存放在 Github.com/niumoo/javaNotes.

<完>

文章持续更新,可以微信搜一搜「 程序猿阿朗 」或访问「程序猿阿朗博客 」第一时间阅读。本文 Github.com/niumoo/JavaNotes 已经收录,有很多知识点和系列文章,欢迎Star。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK