• 一文弄懂JUnit5相关注解


    JUnit5

    JUnit是Java生态系统中最流行的单元测试框架之一。JUnit5版本包含许多令人兴奋的创新,其目标是支持Java8和更高版本中的新功能,并支持多种不同风格的测试。

    Maven依赖

    启动JUnit5.x.0非常简单;我们只需要将以下依赖项添加到pom.xml中:

    <dependency>
        <groupId>org.junit.jupitergroupId>
        <artifactId>junit-jupiter-engineartifactId>
        <version>5.8.1version>
        <scope>testscope>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    此外,集成开发工具,如Eclipse、IntelliJ 支持直接运行JUnit单元测试。新版本IntelliJ默认支持JUnit5。当然,开发者也可以使用Maven Test目标运行测试。

    JUnit5 架构

    JUnit5由三个不同子项目的不同模块组成, JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

    JUnit Platform

    • JUnit Platform 负责在JVM上启动测试框架。它在JUnit及其客户端(如构建工具)之间定义了一个稳定而强大的接口。
    • JUnit Platform 轻松地将客户端与JUnit集成,以发现和执行测试。
    • 定义了TestEngine API,用于开发在JUnit平台上运行的测试框架。通过实现自定义测试引擎,我们可以将第三方测试库直接插入JUnit。

    JUnit Jupiter

    本模块包括用于在JUnit 5中编写测试的新编程和扩展模型。与JUnit 4相比,新注释如下:

    • @TestFactory – 表示作为动态测试的测试工厂的方法
    • @DisplayName – 定义测试类或测试方法的自定义显示名称
    • @Nested – 表示带注释的类是嵌套的非静态测试类
    • @Tag – 过滤筛选测试标记
    • @ExtendWith – 注册自定义扩展
    • @BeforeEach – 表示将在每个测试方法之前执行带注释的方法(相当于之前的@before)
    • @AfterEach –表示将在每个测试方法之后执行带注释的方法(相当于之前的@after)
    • @BeforeAll – 表示注释的方法将在当前类中的所有测试方法之前执行(相当于之前的@BeforeClass)
    • @AfterAll – 表示注释方法将在当前类中的所有测试方法之后执行(相当于之前的@AfterClass)
    • @Disable – 禁用测试类或方法(相当于之前的@Ignore)

    JUnit Vintage

    JUnit Vintage支持在JUnit5平台上运行基于JUnit3和JUnit4的测试。

    基础注解

    @BeforeAll @BeforeEach

    package com.andy.spring.boot.docker.junit5;
    
    import org.junit.jupiter.api.BeforeAll;
    import org.junit.jupiter.api.BeforeEach;
    import org.junit.jupiter.api.Test;
    
    
    public class JunitTest {
    
        @BeforeAll
        public static void setUp(){
            System.out.println("只运行一次,在所有的测试方法执行前运行");
        }
    
        @BeforeEach
        public void init() {
            System.out.println("@BeforeEach - 在每个测试方法之前都会运行");
        }
    
        @Test
        public void test1() {
            System.out.println("test - 执行Test 1 测试方法");
        }
    
        @Test
        public void test2() {
            System.out.println("test - 执行Test 2 测试方法");
        }
    }
    
    • 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

    如下图,执行在测试类上执行 Run JunitTest. 注意:@BeforeAll 注解方法必须为静态方法,使用static关键字修饰

    在这里插入图片描述

    @DisplayName @Disabled

    package com.andy.spring.boot.docker.junit5;
    
    import org.junit.jupiter.api.*;
    
    
    public class JunitTest2 {
    
        @DisplayName("Single test successful")
        @Test
        void testSingleSuccessTest() {
            System.out.println("Success");
        }
    
        @Test
        @Disabled("Not implemented yet")
        void testShowSomething() {
            System.out.println("testShowSomething");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    如下图,执行在测试类上执行 Run JunitTest2, 使用 @Disabled 注解的测试方法被禁用并没有执行

    在这里插入图片描述

    @AfterEach @AfterAll

    package com.andy.spring.boot.docker.junit5;
    
    import org.junit.jupiter.api.*;
    
    
    public class JunitTest3 {
    
        @AfterAll
        public static void setUp(){
            System.out.println("只运行一次,在所有的测试方法执行后运行");
        }
    
        @AfterEach
        public void init() {
            System.out.println("@BeforeEach - 在每个测试方法之后都会运行");
        }
    
        @Test
        public void test1() {
            System.out.println("test - 执行Test 1 测试方法");
        }
    
        @Test
        public void test2() {
            System.out.println("test - 执行Test 2 测试方法");
        }
    }
    
    • 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

    这两个注解跟之前的@BeforeAll、@BeforeEach效果类似,只是执行顺序相反。注意:@BeforeAll 注解方法必须为静态方法,使用static关键字修饰

    @ParameterizedTest

    表示方法是参数化测试。除非重写这些方法,否则将继承这些方法。

    
    import org.junit.jupiter.params.ParameterizedTest;
    import org.junit.jupiter.params.provider.ValueSource;
    import org.springframework.util.StringUtils;
    
    import static org.junit.jupiter.api.Assertions.assertTrue;
    
    public class JunitTest8 {
    
        @ParameterizedTest
        @ValueSource(strings = { "racecar", "radar", "able was I ere I saw elba" })
        void palindromes(String candidate) {
            assertTrue(!StringUtils.isEmpty(candidate));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    如下图,将预定义的参数逐个传入测试方法执行

    在这里插入图片描述

    @RepeatedTest

    @RepeatedTest(10)
        void repeatedTest() {
            System.out.println("hello world");
        }
    
    • 1
    • 2
    • 3
    • 4

    如下执行结果,表示被测试方法可重复执行

    在这里插入图片描述

    @TestClassOrder

    @Nested标注的嵌套试类中,使用@TestClassOrder指定不同类的执行顺序。这样的注释是继承的。

    import org.junit.jupiter.api.ClassOrderer;
    import org.junit.jupiter.api.Nested;
    import org.junit.jupiter.api.Order;
    import org.junit.jupiter.api.Test;
    import org.junit.jupiter.api.TestClassOrder;
    
    @TestClassOrder(ClassOrderer.OrderAnnotation.class)
    class OrderedNestedTestClassesDemo {
    
        @Nested
        @Order(1)
        class PrimaryTests {
    
            @Test
            void test1() {
                System.out.println("test1");
            }
        }
    
        @Nested
        @Order(2)
        class SecondaryTests {
    
            @Test
            void test2() {
                System.out.println("test2");
            }
        }
    }
    
    • 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

    @TestMethodOrder

    真正的单元测试通常不应依赖于它们的执行顺序,但有时需要强制执行特定的测试方法执行顺序 — 例如,在编写集成测试或功能测试时,测试顺序很重要。

    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;
    
    @TestMethodOrder(OrderAnnotation.class)
    class OrderedTestsDemo {
    
        @Test
        @Order(1)
        void nullValues() {
            System.out.println(" test 1");
        }
    
        @Test
        @Order(2)
        void emptyValues() {
            System.out.println(" test 2");
        }
    
        @Test
        @Order(3)
        void validValues() {
            System.out.println(" test 3");
        }
    
    }
    
    • 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

    @TestInstance

    在学习这个注解之前,我们先看以下的测试代码

    class AdditionTest {
    
        private int sum = 1;
    
        @Test
        void addingTwoReturnsThree() {
            sum += 2;
            assertEquals(3, sum);
        }
    
        @Test
        void addingThreeReturnsFour() {
            sum += 3;
            assertEquals(4, sum);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在测试类上按照顺序依次执行这两个测试方法,均测试通过。这是由于在默认情况下,每个测试方法执行之前,sum都会被初始化一次,赋值为1。那么有没有可能需要保留之前测试方法计算之后的值呢?

    @TestInstance就是干这个用的,它有两个设置级别:

    • TestInstance.Lifecycle.PER_METHOD - 变量在测试方法之前重新初始化,默认值
    • TestInstance.Lifecycle.PER_CLASS - 变量的作用范围在每个测试类,即会保留之前测试方法计算后的值

    当在测试类上新增 @TestInstance(TestInstance.Lifecycle.PER_CLASS) ,测试结果就不正常了(如下图,理由是保留之前的计算值)

    在这里插入图片描述

    @Tag

    测试类、测试方法都可以增加 @Tag 注解标记,这些标记稍后可用于过滤测试发现和执行。

    @Test
    @Tag("IntegrationTest")
    public void testAddEmployeeUsingSimpelJdbcInsert() {
    }
    
    @Test
    @Tag("UnitTest")
    public void givenNumberOfEmployeeWhenCountEmployeeThenCountMatch() {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    假设定义以上两个测试方法,并打上不同的标记。后续可以使用标记过滤特定场景的测试要求,如下:

    @SelectPackages("com.baeldung.tags")
    @IncludeTags("UnitTest")
    public class EmployeeDAOUnitTestSuite {
    }
    
    • 1
    • 2
    • 3
    • 4

    运行 com.baeldung.tags 包下标记为 UnitTest 的所有测试方法

    @ExtendWith

    开发者使用@ExtendWith注解扩展一个或多个扩展类。其目的是扩展测试类、测试方法的表现行为,方便模块化测试工作中的代码复用。

    • 定义扩展类

      import org.junit.jupiter.api.extension.AfterEachCallback;
      import org.junit.jupiter.api.extension.BeforeEachCallback;
      import org.junit.jupiter.api.extension.ExtensionContext;
      
      public class MyExtension implements BeforeEachCallback, AfterEachCallback {
      
          @Override
          public void beforeEach(ExtensionContext context) throws Exception {
              System.out.println("MyExtension.beforeEach()");
          }
      
          @Override
          public void afterEach(ExtensionContext context) throws Exception {
              System.out.println("MyExtension.afterEach()");
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
    • 扩展测试类

      import org.junit.jupiter.api.Test;
      import org.junit.jupiter.api.extension.ExtendWith;
      
      @ExtendWith(MyExtension.class)
      public class JUnit5ExtensionTest {
          @Test
          void test1() {
              System.out.println("  test1()");
          }
      
          @Test
          void test2() {
              System.out.println("  test2()");
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15

    @RegisterExtension

    开发者可以将此注释应用于测试类中的字段。这种方法的一个优点是,我们可以直接将扩展作为测试内容中的对象进行访问。JUnit将在适当的阶段调用扩展方法。例如,如果扩展实现BeforeEachCallback,JUnit将在执行测试方法之前调用其相应的接口方法。

    • 定义扩展

      
      import org.junit.jupiter.api.extension.BeforeAllCallback;
      import org.junit.jupiter.api.extension.BeforeEachCallback;
      import org.junit.jupiter.api.extension.ExtensionContext;
      
      public class LoggingExtension implements
              BeforeAllCallback, BeforeEachCallback {
      
          // logger, constructor etc
      
          @Override
          public void beforeAll(ExtensionContext extensionContext)
                  throws Exception {
              System.out.println("beforeAll : " + extensionContext.getDisplayName());
          }
      
          @Override
          public void beforeEach(ExtensionContext extensionContext) throws Exception {
              System.out.println("beforeEach : " + extensionContext.getDisplayName());
          }
      
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
    • 注册扩展

      import org.junit.jupiter.api.Test;
      import org.junit.jupiter.api.extension.RegisterExtension;
      
      public class RegisterExtensionTest {
      
          @RegisterExtension
          static LoggingExtension staticExtension = new LoggingExtension();
      
          @Test
          public void demoTest() {
              // assertions
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
    • 验证测试

      在这里插入图片描述

    JUnit 断言

    JUnit5在语法上使用Java8的一些新特性,尤其是lambda表达式。

    Assertions

    断言已移动到org.unit.jupiter.api。断言和已显著改进。如前所述,开发者可以在断言中使用lambdas:

    public class JunitTest4 {
    
        @Test
        void lambdaExpressions() {
            List<Integer> numbers = Arrays.asList(1, 2, 3);
            assertTrue(numbers.stream().mapToInt(t -> t.intValue()).sum() > 5, () -> "Sum should be greater than 5");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    尽管上面的示例很简单,但对断言消息使用lambda表达式的一个优点是它的求值很慢,如果消息构造很昂贵,这可以节省时间和资源。

    开发者还可以使用assertAll()对断言进行分组,这将使用MultipleFailuresError报告组内的任何失败断言:

    @Test
        void groupAssertions() {
            int[] numbers = {0, 1, 2, 3, 4};
            assertAll("numbers",
                    () -> assertEquals(numbers[0], 1),
                    () -> assertEquals(numbers[3], 3),
                    () -> assertEquals(numbers[4], 1)
            );
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    执行结果如下:

    在这里插入图片描述

    Assumptions

    假设仅在满足某些条件时用于运行测试。这通常用于测试正常运行所需外部依赖条件,但与所测试的内容没有直接关系。开发者可以使用assumeTrue()、assumeFalse()和assumeThat()声明一个假设:

    import org.junit.jupiter.api.Test;
    import static org.junit.jupiter.api.Assertions.*;
    import static org.junit.jupiter.api.Assumptions.*;
    
    public class JunitTest5 {
    
        @Test
        void trueAssumption() {
            assumeTrue(5 > 1);
            assertEquals(5 + 2, 7);
        }
    
        @Test
        void falseAssumption() {
            assumeFalse(5 < 1);
            assertEquals(5 + 2, 7);
        }
    
        @Test
        void assumptionThat() {
            String someString = "Just a string";
            assumingThat(
                    someString.equals("Just a string"),
                    () -> assertEquals(2 + 2, 4)
            );
        }
    }
    
    • 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

    如果假设失败,则抛出TestAbortedException,并跳过测试。

    JUnit5 高级特性

    异常测试

    JUnit 5中有两种异常测试方法,我们可以使用assertThrows()方法实现这两种方法:

    package com.andy.spring.boot.docker.junit5;
    
    import org.junit.jupiter.api.Test;
    import static org.junit.jupiter.api.Assertions.assertEquals;
    import static org.junit.jupiter.api.Assertions.assertThrows;
    
    public class JunitTest6 {
    
        @Test
        void shouldThrowException() {
            Throwable exception = assertThrows(UnsupportedOperationException.class, () -> {
                throw new UnsupportedOperationException("Not supported");
            });
            assertEquals("Not supported", exception.getMessage());
        }
    
        @Test
        void assertThrowsException() {
            String str = null;
            assertThrows(IllegalArgumentException.class, () -> {
                Integer.valueOf(str);
            });
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    第一个示例验证抛出的异常的详细信息,第二个示例验证异常的类型。

    测试组件

    接下来学习一个JUnit5的新特性 Test Suites - 测试组件。将探讨在一个测试场景中聚合多个测试类的概念,以便我们可以一起运行这些测试类。JUnit5提供了两个注释@SelectPackages和@SelectClasses来创建测试组件。

    @Suite
    @SelectPackages("com.baeldung")
    @ExcludePackages("com.baeldung.suites")
    public class AllUnitTest {}
    
    • 1
    • 2
    • 3
    • 4

    @SelectPackage用于指定运行测试组件时要选择的包的名称。在我们的示例中,它将运行所有测试。@ExcludePackages排除指定包下面的所有测试类。

    @Suite
    @SelectClasses({AssertionTest.class, AssumptionTest.class, ExceptionTest.class})
    public class AllUnitClassTest {}
    
    • 1
    • 2
    • 3

    @SelectClasses注解用于指定运行测试套件时要选择的类。请注意,上述三个测试类可以不在同一个package下面。

    Test Suites对于大规模的单元测试比较友好,开发/测试人员只需要编写测试用例,就可以大批量的运行测试代码,提高了开发的效率

    动态测试

    JUnit5的动态测试特性,它允许我们声明和运行运行时生成的测试用例。与静态测试(在编译时定义固定数量的测试用例)相反,动态测试允许我们在运行时动态定义测试用例。动态测试可以通过带有@TestFactory注释的工厂方法生成,请看一下代码:

    package com.andy.spring.boot.docker.junit5;
    
    import org.junit.jupiter.api.DynamicTest;
    import org.junit.jupiter.api.TestFactory;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    import java.util.stream.Stream;
    
    import static org.junit.jupiter.api.Assertions.assertEquals;
    
    public class JunitTest7 {
    
        private List<String> in = new ArrayList<>(Arrays.asList("Hello", "Yes", "No"));
        private List<String> out = new ArrayList<>(Arrays.asList("Cześć", "Tak", "Nie"));
    
        @TestFactory
        Stream<DynamicTest> translateDynamicTestsFromStream() {
            // 1. 循环list数组 每个值都进行动态测试 并赋予特定的显示名称
            return in.stream()
                    .map(word -> DynamicTest.dynamicTest("Test translate " + word, () -> {
                        int id = in.indexOf(word);
                        assertEquals(out.get(id), translate(word));
                    }));
        }
    
        private String translate(String word) {
            if ("Hello".equalsIgnoreCase(word)) {
                return "Cześć";
            } else if ("Yes".equalsIgnoreCase(word)) {
                return "Tak";
            } else if ("No".equalsIgnoreCase(word)) {
                return "Nie";
            }
            return "Error";
        }
    }
    
    • 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

    这个例子非常简单,假设希望使用两个ArrayList来翻译单词,分别命名为in和out。工厂方法必须返回Stream、Collection、Iterable或Iterator。在我们的例子中,我们选择了一个Java8Stream。

    请注意@TestFactory方法不能是私有的或静态的。测试的数量是动态的,它取决于ArrayList的大小。

  • 相关阅读:
    Python中创建类的六重境界
    Debian常用命令
    如何找到联盟营销人员:招募合适会员的10个方法
    国庆day1---消息队列实现进程之间通信方式代码,现象
    【ACWing】3627. 最大差值
    【软件与系统安全笔记】八、软件自我保护
    提分必练!中创教育PMP全真模拟题分享来喽
    让开发回归简单模式-基类封装
    C++设计模式之代理模式(结构型模式)
    如何添加葫芦儿派盘
  • 原文地址:https://blog.csdn.net/u013433591/article/details/128124594