• spring boot 使用单元测试JUnit5


    spring boot 单元测试JUnit5使用断言Assertions和假定Assumptions、嵌套、参数测试

    本文基于spirng boot 2.7.11, 大家注意自己的版本

    Spring Boot 2.2.0 版本开始引入 JUnit 5 作为单元测试默认库

    SpringBoot 2.4 以上版本移除了默认对 Vintage 的依赖

    源码地址:https://gitcode.net/qq_39339588/springboot.git

    1. 标记单元测试类和方法

    @SpringBootTest注解,可以标记为测试类

    @Test注解标记是测试方法

    @DisplayName注解是标记个名称

    @SpringBootTest
    public class HelloWorldTest {
        /**
         * 断言基础
         */
        @Test
        @DisplayName("简单断言")
        public void simple() {
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    2. 断言的简单使用
    1)基本类型和对象的断言
    /**
    * 基本类型和对象的断言
    */
    @Test
    @DisplayName("简单断言")
    public void simple() {
        Assertions.assertEquals(3, 1 + 2, "不相等");
        Assertions.assertNotEquals(3, 1 + 1, "相等了");
    
        //断言对象
        Assertions.assertNotSame(new Object(), new Object(), "对象相同");
        Object o = new Object();
        Assertions.assertSame(o, o, "断言不相等了");
    
        Assertions.assertFalse(1 > 2);
        Assertions.assertTrue(1 < 2);
        Assertions.assertNull(null);
        Assertions.assertNotNull(new Object());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    2)数组断言
    @DisplayName("数组断言")
    @Test
    public void array() {
        // 断言两个数组对象不相等
        Assertions.assertNotEquals(new int[]{1, 2}, new int[]{1, 2});
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    3)组合断言
    @DisplayName("组合断言")
    @Test
    public void all() {
        // 断言所多个断言方法都正确
        Assertions.assertAll("组合断言",
                () -> Assertions.assertEquals(2, 1 + 1),
                () -> Assertions.assertTrue(1 > 0)
        );
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    4)异常断言
    @DisplayName("异常断言")
    @Test
    public void exception() {
        // 断言方法 会出现异常
        ArithmeticException arithmeticException = Assertions.assertThrows(ArithmeticException.class, 
                () -> System.out.println(1 % 0));
        arithmeticException.printStackTrace();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    5)超时断言
    @DisplayName("超时断言")
    @Test
    public void timeOut() {
        // 断言方法执行不会超过1秒
        Assertions.assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(500));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    6)快速失败断言
    @DisplayName("快速失败")
    @Test
    public void failFast() {
        // 触发快速失败后
        AssertionFailedError ex = Assertions.assertThrows(AssertionFailedError.class, () -> {
            Assertions.fail("触发快速失败");
        });
        ex.printStackTrace();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    3. 前置条件断言,假设假定

    执行断言的前提条件,如果假定成功true,才会去执行

    /**
     * 前置条件,假设假定
     */
    @DisplayName("前置条件")
    @Test
    public void assuming() {
        String evn = "dev";
        // 执行断言的前提条件,如果假定成功true,才会去执行下一行,如果假定失败,下边的就不执行了
        Assumptions.assumeTrue(Objects.equals(evn, "dev"));
        Assumptions.assumeFalse(Objects.equals(evn, "prod"));
        System.out.println("dev和不是prod,执行了");
        Assumptions.assumeTrue(Objects.equals(evn, "dev1"));
        System.out.println("dev1,这句会忽略,不会执行输出");
    }
    
    
    /**
     * 假定成功true,才会去执行 执行器方法
     */
    @DisplayName("前置条件That")
    @Test
    public void assumingThat() {
        // 假定成功,才会去执行 执行器方法
        Assumptions.assumingThat(
                Objects.equals("dev", "dev"), () -> {
                    System.out.println("in dev");
                }
        );
    }
    
    • 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
    4. 嵌套@Nested

    内部嵌套,每次会先走外层的beforeEach,在走内部的beforeEach;支持嵌套的嵌套

    @DisplayName("单元测试示例")
    @SpringBootTest
    public class HelloWorldTest {
        
        Stack<Object> stack;
    
        /**
         * 内部嵌套,每次会先走外层的beforeEach,在走内部的beforeEach
         */
        @DisplayName("内部嵌套测试")
        @Nested
        class InnerTest {
            @BeforeEach
            void newStack() {
                // 创建一个栈对象
                stack = new Stack<>();
            }
    
            @DisplayName("检查是否为空")
            @Test
            void isEmpty() {
                Assertions.assertTrue(stack.isEmpty());
            }
    
            @DisplayName("抛出一个栈pop异常")
            @Test
            void throwExceptionWhenPop() {
                Assertions.assertThrows(EmptyStackException.class, stack::pop);
            }
    
            @DisplayName("抛出一个栈peek异常")
            @Test
            void throwExceptionWhenPeek() {
                Assertions.assertThrows(EmptyStackException.class, stack::peek);
            }
    
            @Nested
            @DisplayName("内部的内部嵌套测试")
            class InnerInnerTest {
                String item = "item";
    
                @BeforeEach
                void pushAnItem() {
                    // 放入一个元素
                    stack.push(item);
                }
    
                @DisplayName("不为空了")
                @Test
                void isNotEmpty() {
                    Assertions.assertFalse(stack.isEmpty());
                }
    
                @DisplayName("取出一个来,用pop")
                @Test
                void returnItemByPop() {
                    Assertions.assertEquals(item, stack.pop());
                    Assertions.assertTrue(stack.isEmpty());
                }
    
                @DisplayName("取出一个来,用peek")
                @Test
                void returnItemByPeek() {
                    Assertions.assertEquals(item, 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
    5. 参数化测试

    参数化测试,让单元测试的方法支持接收传入的参数

    将可以使用不同的参数进行多次单元测试,有多少套参数,就执行多少次单元测试

    而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码

    1)@ValueSource注解,基本类型入参
    /**
     * @ValueSource:为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型
     * @param params
     */
    @DisplayName("参数化测试,值来源")
    @ParameterizedTest
    @ValueSource(strings = {"a", "b", "c"})
    public void parameterizedTest(String params) {
        // 会执行3次,分别执行a,b,c
        System.out.println(params);
        Assertions.assertTrue(StringUtils.isNotBlank(params));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    2)@NullSource注解,null入参
    /**
     * null入参
     *
     * @param params
     */
    @DisplayName("null的入参")
    @ParameterizedTest
    @NullSource
    public void testNull(String params) {
        Assertions.assertNull(params);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    3)@EnumSource注解,枚举入参
    /**
     * 枚举入参,会把枚举遍历一遍,分别执行一次方法
     */
    @DisplayName("枚举的入参")
    @ParameterizedTest
    @EnumSource(TestEnum.class)
    public void testEnumSource(TestEnum params) {
        System.out.println("state:" + params.state + ",msg:" + params.msg);
        // state:1,msg:好
        // state:2,msg:不好
        Assertions.assertNotNull(params);
    }
    
    /**
     * 枚举类
     */
    @Getter
    @AllArgsConstructor
    public enum TestEnum {
        GOOD(1, "好"),
        BAD(2, "不好");
        int state;
        String msg;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    4)@CsvSource注解,多参数入参
    @DisplayName("参数化测试,@CsvSource")
    @ParameterizedTest
    @CsvSource({"a,b,c"})
    void parameterizedTestCsvSource(String a, String b, String c) {
        // 默认使用逗号分隔
        System.out.println(a + b + c);
        Assertions.assertNotNull(a);
    }
    
    @DisplayName("参数化测试,@CsvSource2")
    @ParameterizedTest
    @CsvSource(value = {"a,b,c-D"}, delimiterString = "-")
    void parameterizedTestCsvSource2(String abc, String d) {
        // 默认使用逗号分隔,指定分隔符"-"
        System.out.println(abc);
        System.out.println(d);
        Assertions.assertNotNull(abc);
        // a,b,c
        // D
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    5)@CsvFileSource,参数文件入参

    可以把文件中的内容当做参数,批量来执行,下边附带了my.csv和my2.csv文件内容

    注意,要在测试目录下,新建resources目录,再放入my.csv和my2.csv文件

    /**
     * 参数文件
     *
     * @param methodName
     */
    @DisplayName("参数化测试,@CsvFileSource")
    @ParameterizedTest
    @CsvFileSource(resources = "/my.csv")
    void parameterizedTestWithCsvFileSource(String methodName) {
        // 测试目录下,新建resources目录
        System.out.println(methodName);
        Assertions.assertNotNull(methodName);
    }
    
    @DisplayName("参数化测试,@CsvFileSource")
    @ParameterizedTest
    @CsvFileSource(resources = "/my2.csv", delimiterString = "|")
    void parameterizedTestWithCsvFileSource2(String name, Integer age) {
        // 测试目录下,新建resources目录
        System.out.println(name + ":" + age);
        Assertions.assertNotNull(name);
        // a:18
        // b:17
        // c:20
    }
    
    • 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

    my.csv文件

    1
    2
    3
    4
    
    • 1
    • 2
    • 3
    • 4

    my2.csv

    a|18
    b|17
    c|20
    
    • 1
    • 2
    • 3
    6)@MethodSource注解,方法返回值入参

    把一个方法的返回值,作为测试用例方法的参数

    /**
     * 动态参数
     */
    @DisplayName("参数化测试,方法来源")
    @ParameterizedTest
    @MethodSource("method1") // 如果不指定方法名称,会去找与自己相同名称的静态方法
    public void parameterizedTestWithMethodParam(String params) {
        // MethodSource("method1") 接收这个方法的返回值,之后会执行2次,a,b
        System.out.println(params);
        Assertions.assertNotNull(params);
    }
    
    static Stream<String> method1() {
        return Stream.of("a", "b");
    }
    
    
    @DisplayName("参数化测试,方法来源,多参数")
    @ParameterizedTest
    @MethodSource // 如果不指定方法名称,会去找相同名称的静态方法
    public void parameterizedTestWithMethodMultipleParams(String name, Integer age) {
        System.out.println(name + ":" + age);
        // a:18
        // b:17
        // c:20
        Assertions.assertNotNull(name);
    }
    
    static Stream<Arguments> parameterizedTestWithMethodMultipleParams() {
        return Stream.of(
                Arguments.arguments("a", 18),
                Arguments.arguments("b", 17),
                Arguments.arguments("c", 20)
        );
    }
    
    
    • 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
    7) @ArgumentsSource注解,自定义参数入参
    /**
     * 实现ArgumentsProvider接口,并在测试方法上使用@ArgumentsSource注解。
     */
    @DisplayName("@ArgumentsSource注解")
    @ParameterizedTest
    @ArgumentsSource(MyArgumentsProvider.class)
    public void testArgumentsSource(String name, int age) {
        System.out.println(name + ":" + age);
        // aa:18
        // bb:20
        // cc:30
        Assertions.assertNotNull(name);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
  • 相关阅读:
    基于SSM框架的校园疫情防控健康打卡系统
    发展前景好、薪资高,计算机行业成为许多人改变命运的首选!
    BUU 加固题 AWDP Fix 持续更新中
    基于springboot高校学生健康打卡系统021009
    Scala变量和数据类型(01)
    SSL证书续费要如何操作
    y9000k 安装ubuntu
    Windows按键win和alt不小心互换了
    软考架构设计师知识点
    什么是最小误差测量
  • 原文地址:https://blog.csdn.net/qq_39339588/article/details/130901703