• SpringBoot-单元测试


    目录

    一、JUnit5

    二、常用注解

    三、断言(assertions)

    3.1 简单断言

    3.2 数组断言

    3.3 组合断言

    3.4  异常断言

    3.5 超时异常

    四、前置条件

    五、嵌套测试

    六、参数化测试


    一、JUnit5

            JUnit5与之前版本的Junit框架有很大的不同。由三个不同子项目的几个不同模块组成。

            JUnit Platform: Junit Platform是在JVM上启动测试框架的基础,不仅支持Junit自制的测试引擎,其他测试引擎也都可以接入。

            JUnit Jupiter: JUnit Jupiter提供了JUnit5的新的编程模型,是JUnit5新特性的核心。内部 包含了一个测试引擎,用于在Junit Platform上运行。

            JUnit Vintage: 由于JUint已经发展多年,为了照顾老的项目,JUnit Vintage提供了兼容JUnit4.x,Junit3.x的测试引擎。

            注意: 

    • Junit类具有Spring的功能,@Autowired、比如 @Transactional 标注测试方法,测试完成后自动回滚
    •  SpringBoot 2.4 以上版本移除了默认对 Vintage 的依赖。如果需要兼容junit4需要自行引入
    1. <dependency>
    2. <groupId>org.junit.vintagegroupId>
    3. <artifactId>junit-vintage-engineartifactId>
    4. <scope>testscope>
    5. <exclusions>
    6. <exclusion>
    7. <groupId>org.hamcrestgroupId>
    8. <artifactId>hamcrest-coreartifactId>
    9. exclusion>
    10. exclusions>
    11. dependency>

            引入Junit5:

    1. <dependency>
    2. <groupId>org.springframework.bootgroupId>
    3. <artifactId>spring-boot-starter-testartifactId>
    4. <scope>testscope>
    5. dependency>

             测试:

    1. @SpringBootTest
    2. class testJunit5{
    3. @Test
    4. void testHello() {
    5. }
    6. }

    二、常用注解

    • @Test :表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
    • @ParameterizedTest :表示方法是参数化测试,下方会有详细介绍
    • @RepeatedTest :表示方法可重复执行,下方会有详细介绍
    • @DisplayName :为测试类或者测试方法设置展示名称
    • @BeforeEach :表示在每个单元测试之前执行
    • @AfterEach :表示在每个单元测试之后执行
    • @BeforeAll :表示在所有单元测试之前执行
    • @AfterAll :表示在所有单元测试之后执行
    • @Tag :表示单元测试类别,类似于JUnit4中的@Categories
    • @Disabled :表示测试类或测试方法不执行,类似于JUnit4中的@Ignore
    • @Timeout :表示测试方法运行如果超过了指定时间将会返回错误
    • @ExtendWith :为测试类或测试方法提供扩展类引用

    三、断言(assertions)

            断言(assertions)是测试方法中的核心部分,用来对测试需要满足的条件进行验证这些断言方法都是 org.junit.jupiter.api.Assertions 静态方法检查业务逻辑返回的数据是否合理。所有的测试运行结束以后,会有一个详细的测试报告

    3.1 简单断言

            用来对单个值进行简单的验证

    方法

    说明

    assertEquals

    判断两个对象或两个原始类型是否相等

    assertNotEquals

    判断两个对象或两个原始类型是否不相等

    assertSame

    判断两个对象引用是否指向同一个对象

    assertNotSame

    判断两个对象引用是否指向不同的对象

    assertTrue

    判断给定的布尔值是否为 true

    assertFalse

    判断给定的布尔值是否为 false

    assertNull

    判断给定的对象引用是否为 null

    assertNotNull

    判断给定的对象引用是否不为 null

            方法参数:

                    测试值

                    期望值

                    测试值与期望值不符时打印的信息

    3.2 数组断言

            通过 assertArrayEquals 方法来判断两个对象原始类型数组是否相等

    1. @Test
    2. @DisplayName("array assertion")
    3. public void array() {
    4. assertArrayEquals(new int[]{1, 2}, new int[] {1, 2});
    5. }

    3.3 组合断言

            assertAll 方法接受多个 org.junit.jupiter.api.Executable 函数式接口实例作为要验证的断言,可以通过 lambda 表达式很容易的提供这些断言

    1. @Test
    2. @DisplayName("assert all")
    3. public void all() {
    4. assertAll("Math",
    5. () -> assertEquals(2, 1 + 1),
    6. () -> assertTrue(1 > 0)
    7. );
    8. }

    3.4  异常断言

    1. @Test
    2. @DisplayName("异常测试")
    3. public void exceptionTest() {
    4. ArithmeticException exception = Assertions.assertThrows(
    5. //扔出断言异常
    6. ArithmeticException.class, () -> System.out.println(1 % 0));
    7. }

    3.5 超时异常

             Assertions.assertTimeout() 为测试方法设置了超时时间

    1. @Test
    2. @DisplayName("超时测试")
    3. public void timeoutTest() {
    4. //如果测试方法时间超过1s将会异常
    5. Assertions.assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(500));
    6. }

    四、前置条件

            前置条件(assumptions【假设】)类似于断言,不同之处在于不满足的断言会使得测试方法失败,而不满足的前置条件只会使得测试方法的执行终止。前置条件可以看成是测试方法执行的前提,当该前提不满足时,就没有继续执行的必要。

            assumeTrue assumFalse 确保给定的条件为 true 或 false,不满足条件会使得测试执行终止。

            assumingThat 的参数是表示条件的布尔值和对应的 Executable 接口的实现对象。只有条件满足时,Executable 对象才会被执行;当条件不满足时,测试执行并不会终止。

    1. @DisplayName("前置条件")
    2. public class AssumptionsTest {
    3. private final String environment = "DEV";
    4. @Test
    5. @DisplayName("simple")
    6. public void simpleAssume() {
    7. assumeTrue(Objects.equals(this.environment, "DEV"));
    8. assumeFalse(() -> Objects.equals(this.environment, "PROD"));
    9. }
    10. @Test
    11. @DisplayName("assume then do")
    12. public void assumeThenDo() {
    13. assumingThat(
    14. Objects.equals(this.environment, "DEV"),
    15. () -> System.out.println("In DEV")
    16. );
    17. }
    18. }

    五、嵌套测试

            JUnit 5 可以通过 Java 中的内部类@Nested 注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起。在内部类中可以使用@BeforeEach @AfterEach 注解,而且嵌套的层次没有限制。对于before/after只对于同层或者更里层的测试有效,对于外层不生效。

    1. @DisplayName("A stack")
    2. class TestingAStackDemo {
    3. Stack stack;
    4. @Test
    5. @DisplayName("is instantiated with new Stack()")
    6. void isInstantiatedWithNew() {
    7. new Stack<>();
    8. }
    9. @Nested
    10. @DisplayName("when new")
    11. class WhenNew {
    12. @BeforeEach
    13. void createNewStack() {
    14. stack = new Stack<>();
    15. }
    16. @Test
    17. @DisplayName("is empty")
    18. void isEmpty() {
    19. assertTrue(stack.isEmpty());
    20. }
    21. @Test
    22. @DisplayName("throws EmptyStackException when popped")
    23. void throwsExceptionWhenPopped() {
    24. assertThrows(EmptyStackException.class, stack::pop);
    25. }
    26. @Test
    27. @DisplayName("throws EmptyStackException when peeked")
    28. void throwsExceptionWhenPeeked() {
    29. assertThrows(EmptyStackException.class, stack::peek);
    30. }
    31. @Nested
    32. @DisplayName("after pushing an element")
    33. class AfterPushing {
    34. String anElement = "an element";
    35. @BeforeEach
    36. void pushAnElement() {
    37. stack.push(anElement);
    38. }
    39. @Test
    40. @DisplayName("it is no longer empty")
    41. void isNotEmpty() {
    42. assertFalse(stack.isEmpty());
    43. }
    44. @Test
    45. @DisplayName("returns the element when popped and is empty")
    46. void returnElementWhenPopped() {
    47. assertEquals(anElement, stack.pop());
    48. assertTrue(stack.isEmpty());
    49. }
    50. @Test
    51. @DisplayName("returns the element when peeked but remains not empty")
    52. void returnElementWhenPeeked() {
    53. assertEquals(anElement, stack.peek());
    54. assertFalse(stack.isEmpty());
    55. }
    56. }
    57. }
    58. }
    59. 六、参数化测试

              利用@ValueSource等注解,指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码。

      @ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型

      @NullSource: 表示为参数化测试提供一个null的入参

      @EnumSource: 表示为参数化测试提供一个枚举入参

      @CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参

      @MethodSource:表示读取指定方法返回值作为参数化测试入参(注意方法返回需要是一个)

      1. @ParameterizedTest
      2. @ValueSource(strings = {"one", "two", "three"})
      3. @DisplayName("参数化测试1")
      4. public void parameterizedTest1(String string) {
      5. System.out.println(string);
      6. Assertions.assertTrue(StringUtils.isNotBlank(string));
      7. }
      8. @ParameterizedTest
      9. @MethodSource("method") //指定方法名
      10. @DisplayName("方法来源参数")
      11. public void testWithExplicitLocalMethodSource(String name) {
      12. System.out.println(name);
      13. Assertions.assertNotNull(name);
      14. }
      15. static Stream method() {
      16. return Stream.of("apple", "banana");
      17. }

              当然如果参数化测试仅仅只能做到指定普通的入参还达不到让我觉得惊艳的地步。让我真正感到他的强大之处的地方在于他可以支持外部的各类入参。如:CSV,YML,JSON 文件甚至方法的返回值也可以作为入参。只需要去实现ArgumentsProvider接口,任何外部文件都可以作为它的入参。

    60. 相关阅读:
      FastApi和Ajax传输图片
      GPT-SoVITS声音训练报错ZeroDivisionError: division by zero
      2022-11-23
      MySQL基础(2)
      WebRTC中 setup:actpass、active、passive
      Spring5从入门到精通(一)
      DOPE修饰岩藻多糖 Fucoidan-DOPE 岩藻多糖-二油酰基磷脂酰乙醇胺
      JVM 第四部分—垃圾回收概念 和 算法 1
      1 如何入门TensorFlow
      Java连接PostGreSql
    61. 原文地址:https://blog.csdn.net/weixin_62427168/article/details/126172549