• JUnit 5 单元测试教程


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

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

    在这里插入图片描述

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

    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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BAiVK3Sz-1668733594320)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/84d9e4521a6c47538d747c300b9420ad~tplv-k3u1fbpfcp-zoom-1.image)]

    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.jupitergroupId>
        <artifactId>junit-jupiterartifactId>
      	<version>5.9.1version>
        <scope>testscope>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    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");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

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

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

    org.opentest4j.AssertionFailedError: 
    Expected :cat
    Actual   :dog
    <Click to see difference>
    
    • 1
    • 2
    • 3
    • 4

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

    @Test
    public void testIsDog2() {
        String name = "dog";
        Assertions.assertEquals(name, "dog");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    testIsDog2 方法测试通过。

    在这里插入图片描述

    @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");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    这会输出:

    初始化,准备测试信息
    is cat
    is dog
    
    • 1
    • 2
    • 3

    @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");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    这会输出:

    初始化,准备测试信息
    开始测试...
    is cat
    开始测试...
    is dog
    
    • 1
    • 2
    • 3
    • 4
    • 5

    @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("结束,准备退出测试");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    这会输出:

    初始化,准备测试信息
    开始测试...
    is cat
    开始测试...
    is dog
    结束,准备退出测试
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    @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("结束,准备退出测试");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    这会输出:

    初始化,准备测试信息
    开始测试...
    is cat
    测试完毕...
    开始测试...
    is dog
    测试完毕...
    结束,准备退出测试
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    @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("结束,准备退出测试");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    这会输出:

    初始化,准备测试信息
    开始测试...
    is cat
    测试完毕...
    
    由于xx原因,关闭 testIsDog 测试
    结束,准备退出测试
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    @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("结束,准备退出测试");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

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

    在这里插入图片描述

    @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");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    这会输出:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LvYoVAim-1668733594323)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c451427fdaa848518e1276b6094f9ff1~tplv-k3u1fbpfcp-zoom-1.image)]

    @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");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    这会输出:

    is cat
    is dog
    
    • 1
    • 2

    其他注解

    @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");
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

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

    这会输出:

    org.opentest4j.AssertionFailedError: name is not null ==> 
    Expected :null
    Actual   :cat
    
    
    • 1
    • 2
    • 3
    • 4

    预期是一个 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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 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());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

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

    <build>
         <plugins>
             <plugin>
                 <artifactId>maven-surefire-pluginartifactId>
                 <version>2.22.2version>
             plugin>
             <plugin>
                 <artifactId>maven-failsafe-pluginartifactId>
                 <version>2.22.2version>
             plugin>
         plugins>
    build>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    执行测试命令: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] ------------------------------------------------------------------------
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

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

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

    <完>

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

  • 相关阅读:
    Ubuntu基础操作
    技术杂记:nginx进程的view和kill / linux命令
    C++ //练习 13.34 编写本节所描述的Message。
    SpringBoot + layui 框架实现一周免登陆功能
    python基于协同过滤推荐算法的电影观后感推荐管理系统的设计
    9月总共面试34次,我人麻了....
    如何在功能、特点、价格和性能方面选择PDF编辑器?
    GB28181控制、传输流程和协议接口之注册|注销和技术实现
    R统计绘图-线性混合效应模型详解(理论、模型构建、检验、选择、方差分解及结果可视化)
    分布式锁使用
  • 原文地址:https://blog.csdn.net/u013735734/article/details/127915358