• 【Spring Boot】单元测试


    一、常用注解

    官方文档:Junit5官网指导

    • @Test :表示此方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一,不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
    • @ParameterizedTest:参数化测试使用注解
    • @RepeatedTest :表示测试方法可重复执行,value表示重复执行次数
    • @DisplayName :为测试类或者测试方法设置展示名称
    • @BeforeEach :表示在每个单元测试之前执行该方法
    • @AfterEach :表示在每个单元测试之后执行该方法
    • @BeforeAll :表示在所有开始单元测试之前执行,此方法必须是静态方法
    • @AfterAll :表示在所有单元测试完成之后执行,此方法必须是静态方法
    • @Tag :表示单元测试类别,类似于JUnit4中的@Categories
    • @Disabled :表示测试类或测试方法不执行,类似于JUnit4中的@Ignore
    • @Timeout :表示测试方法运行如果超过了指定时间将会返回错误
    • @SpringBootTest:如果测试类想要使用Spring Boot的自动注入功能,例如@Autowired注解等,就需要在测试类上加上此注解
    • @ExtendWith :为测试类或测试方法提供扩展类引用
      类似于@RunWith,@RunWith(JUnit4.class) 就是指用JUnit4来运行,@RunWith(SpringJUnit4ClassRunner.class),让测试运行于Spring测试环境
    package com.decade;
    
    import org.junit.jupiter.api.*;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.jdbc.core.JdbcTemplate;
    
    import java.util.concurrent.TimeUnit;
    
    @DisplayName("测试类的名称为MyTest")
    @SpringBootTest
    public class MyTest {
    
        @Autowired
        private JdbcTemplate jdbcTemplate;
    
        @DisplayName("测试方法名称为test")
        @RepeatedTest(value = 3)
        public void test() {
            System.out.println("测试方法,打印JdbcTemplate类" + jdbcTemplate);
        }
    
        @Test
        @Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
        public void testTimeOut() throws InterruptedException {
            Thread.sleep(600);
        }
    
        @BeforeEach
        public void beforeEach() {
            System.out.println("每个方法前执行");
        }
    
        @AfterEach
        public void afterEach() {
            System.out.println("每个方法后执行");
        }
    
        @BeforeAll
        static void beforeAll() {
            System.out.println("所有方法前执行");
        }
    
        @AfterAll
        static void afterAll() {
            System.out.println("所有方法后执行");
        }
    
        @Disabled
        @Test
        public void disableTest() {
            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
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    二、断言机制

    断言是测试方法中的核心部分,它用于检查业务逻辑返回的数据是否合理,不满足的断言会使得测试方法失败

    如果是多个断言依次执行,只要前面的断言不通过,后面的就不会再执行了

    1、简单断言
    • assertEquals:判断两个对象或两个原始类型是否相等
    • assertNotEquals:判断两个对象或两个原始类型是否不相等
    • assertSame:判断两个对象引用是否指向同一个对象
    • assertNotSame:判断两个对象引用是否指向不同的对象
    • assertTrue:判断给定的布尔值是否为 true
    • assertFalse:判断给定的布尔值是否为 false
    • assertNull:判断给定的对象引用是否为 null
    • assertNotNull:判断给定的对象引用是否不为 null
    2、数组断言

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

    3、组合断言

    通过assertAll方法接受多个函数式接口的实例作为要验证的断言,只要有一个不通过,就算失败

    4、异常断言

    通过assertThrow方法断定某个代码会抛出指定异常

    5、超时异常

    通过assertTimeout断定某个代码的执行时间会超过限制时间

    6、快速失败

    通过fail方法直接使得测试失败

    package com.decade;
    
    import org.junit.jupiter.api.Assertions;
    import org.junit.jupiter.api.DisplayName;
    import org.junit.jupiter.api.Test;
    
    import java.time.Duration;
    import java.time.temporal.ChronoUnit;
    
    @DisplayName("测试类为TestAssert")
    public class TestAssert {
    
        @Test
        @DisplayName("测试普通断言")
        public void testSimpleAssert() {
            Assertions.assertEquals(6, 2+3, "期望值与实际值不符");
            Assertions.assertTrue(1 > 2, "条件不成立");
            Object a = new Object();
            Object b = new Object();
            Assertions.assertSame(a, b, "不是同一个对象");
        }
    
        @Test
        @DisplayName("测试数组断言")
        public void testArray() {
            Assertions.assertArrayEquals(new int[]{1, 2}, new int[]{2,1}, "数组不相等");
        }
    
        @Test
        @DisplayName("测试组合断言")
        public void testCombination() {
            Assertions.assertAll("断言组合1",
                () -> Assertions.assertTrue(1 < 2, "判断条件不成立"),
                () -> Assertions.assertEquals(2, 3, "期望值与实际值不相符"));
        }
    
        @Test
        @DisplayName("测试异常断言")
        public void testException() {
            Assertions.assertThrows(ArithmeticException.class, () -> {
                System.out.println(1/0);
            }, "并没有抛出算数异常");
        }
    
        @Test
        @DisplayName("测试超时断言")
        public void testTimeOut() {
            Assertions.assertTimeout(Duration.of(100, ChronoUnit.MILLIS),
                () -> Thread.sleep(500), "超时");
        }
    
        @Test
        @DisplayName("测试快速失败fail")
        public void shouldFail() {
            Assertions.fail("This should fail");
        }
    }
    
    • 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
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    三、前置条件

    前置和断言的不同之处在于,不满足的断言会使得测试方法失败,而不满足的前置条件只会使得测试方法的执行终止

    前置条件可以看成是测试方法执行的前提,当该前提不满足时,就没有继续执行的必要

    package com.decade;
    
    import org.junit.jupiter.api.Assumptions;
    import org.junit.jupiter.api.DisplayName;
    import org.junit.jupiter.api.Test;
    
    @DisplayName("测试类为Test")
    public class Test {
        @Test
        @DisplayName("测试前置条件")
        public void testAssumptions() {
            Assumptions.assumeTrue(1 > 2, "条件不成立,后续步骤不再执行");
            System.out.println("前置条件成立,继续执行到此步骤");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    四、嵌套测试

    可以理解成测试套娃,需要注意的是,外层的测试方法无法驱动内层的测试方法,去执行内层测试方法的beforeEach、beforeAll、afterEach、afterAll

    package com.decade;
    
    import org.junit.jupiter.api.*;
    
    import java.util.EmptyStackException;
    import java.util.Stack;
    
    @DisplayName("进行嵌套测试")
    public class TestAStackDemo {
    
        Stack<Object> stack;
    
        @Test
        @DisplayName("is instantiated with new Stack()")
        void isInstantiatedWithNew() {
            new Stack<>();
            // 外层的test方法无法驱动内层的test,也就无法驱动下方的BeforeEach去实例化stack,所以这里会报空
            Assertions.assertNull(stack);
        }
    
        @Nested
        @DisplayName("when new")
        class WhenNew {
    
            @BeforeEach
            void createNewStack() {
                stack = new Stack<>();
            }
    
            @Test
            @DisplayName("is empty")
            void isEmpty() {
                Assertions.assertTrue(stack.isEmpty());
            }
    
            @Test
            @DisplayName("throws EmptyStackException when popped")
            void throwsExceptionWhenPopped() {
                Assertions.assertThrows(EmptyStackException.class, stack::pop);
            }
    
            @Test
            @DisplayName("throws EmptyStackException when peeked")
            void throwsExceptionWhenPeeked() {
                Assertions. assertThrows(EmptyStackException.class, stack::peek);
            }
    
            @Nested
            @DisplayName("after pushing an element")
            class AfterPushing {
    
                String anElement = "an element";
    
                // 内层的test可以驱动外层的test,这里会驱动上方的beforeEach去实例化stack
                @BeforeEach
                void pushAnElement() {
                    stack.push(anElement);
                }
    
                @Test
                @DisplayName("it is no longer empty")
                void isNotEmpty() {
                    Assertions.assertFalse(stack.isEmpty());
                }
    
                @Test
                @DisplayName("returns the element when popped and is empty")
                void returnElementWhenPopped() {
                    Assertions.assertEquals(anElement, stack.pop());
                    Assertions.assertTrue(stack.isEmpty());
                }
    
                @Test
                @DisplayName("returns the element when peeked but remains not empty")
                void returnElementWhenPeeked() {
                    Assertions.assertEquals(anElement, stack.peek());
                    Assertions.assertFalse(stack.isEmpty());
                }
            }
        }
    }
    
    • 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
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81

    五、参数化测试

    • @ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型
    • @NullSource: 表示为参数化测试提供一个null的入参
    • @EnumSource: 表示为参数化测试提供一个枚举入参
    • @CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参
    • @MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)
    package com.decade;
    
    import org.junit.jupiter.params.ParameterizedTest;
    import org.junit.jupiter.params.provider.MethodSource;
    import org.junit.jupiter.params.provider.ValueSource;
    
    import java.util.stream.Stream;
    
    @DisplayName("进行参数化测试")
    public class Test {
        @ParameterizedTest
        @ValueSource(strings = {"decade", "hhh", "LOL"})
        @DisplayName("参数化测试,传入String类型依次测试")
        public void testParameter(String variables) {
            System.out.println(variables);
        }
    
        @ParameterizedTest
        // 此处为方法名,方法必须返回stream流,并且是静态的
        @MethodSource("creatStream")
        @DisplayName("参数化测试,传入String类型依次测试")
        public void testParameterIsMethod(String variables) {
            System.out.println(variables);
        }
    
        static Stream<String> creatStream() {
            return Stream.of("decade", "hhh", "basketball");
        }
    }
    
    • 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

    测试结果如图
    在这里插入图片描述

    如有错误,欢迎指正!!!

  • 相关阅读:
    postgresql数据库定时备份到远程数据库
    写给正在互联网经历孤独和迷茫的你
    后台基础权限框架搭建实现[木字楠博客]
    c++ grpc 第一个用例
    Java之运算符(4)
    k8s入门到实战--跨服务调用
    rabbitmq 集群搭建
    32岁清华美女博导获奖百万,曾研制世界首台咽拭子采样机器人
    kubernetes集群搭建
    极盾故事|某头部私募基金“扩展检测响应XDR平台”建设
  • 原文地址:https://blog.csdn.net/Decade0712/article/details/127605480