草庐IT

SpringBoot 单元测试——JUnit5

我的眼里只有学习 2023-05-20 原文

目录

1、JUnit5概述

1.1、JUnit5 构成

1.2、JUnit5 配置

1.2.1、导入 Junit5 开发场景

 1.2.2、Junit5 开发场景自动导入依赖项

2、JUnit5 使用

2.1、Jnuit5 测试代码开发

2.1.1、测试代码格式

2.1.2、测试样例 

2.2、JUnit5常用注解

 2.2.1、@Test :表示方法是测试方法。

 2.2.2、@DisplayName :为测试类或者测试方法设置展示名称

 2.2.3、@BeforeEach、@AfterEach、@BeforeAll、@AfterAll

 2.2.4、@Disabled :表示测试类或测试方法不执行

 2.2.5、@Timeout :表示测试方法运行如果超过了指定时间将会返回错误

 2.2.6、@RepeatedTest :表示方法可重复执行

 2.2.7、其他注解

2.3、Jnuit5 断言(assertions)

 2.3.1、简单断言

 2.3.2、数组断言

 2.3.3、组合断言

 2.3.4、异常断言

 2.3.5、超时断言

 2.3.6、快速失败

2.4、前置条件

2.5、嵌套测试

2.6、参数化测试


1、JUnit5概述

1.1、JUnit5 构成

 JUnit5 由三个不同子项目的几个不同模块组成。

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

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

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

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

1.2、JUnit5 配置

1.2.1、导入 Junit5 开发场景

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

 1.2.2、Junit5 开发场景自动导入依赖项

 

2、JUnit5 使用

2.1、Jnuit5 测试代码开发

2.1.1、测试代码格式

 @SpringBootTest注解:添加在需要依赖springboot框架的测试类上,

                                        不然不能使用Springboot的相关开发功能

@Test注解:添加在测试方法上

@SpringBootTest
class Boot05WebAdminApplicationTests {

    @Test
    void contextLoads() {

    }
}

2.1.2、测试样例 

@Slf4j
@SpringBootTest
class SpringBootThymeleafApplicationTests {

    @Autowired
    JdbcTemplate jdbcTemplate;

    @Autowired
    DataSource dataSource;

    @Autowired
    UserMapper userMapper;

    @Autowired
    StringRedisTemplate redisTemplate;

    @Test
    void contextLoads() {
        Long along = jdbcTemplate.queryForObject("select count(*) from account", long.class);
        log.info("记录总数{}", along);
        log.info("数据源类型{}", dataSource.getClass());
    }

    @Test
    void testUserMapper() {
        User user = userMapper.selectById(1);
        log.info("用户信息{}", user);
    }

    @Test
    void testRedis() {
        ValueOperations<String, String> operations = redisTemplate.opsForValue();

        operations.set("hello", "world");

        String hello = operations.get("hello");
        System.out.println(hello);
    }
}

2.2、JUnit5常用注解

 2.2.1、@Test :表示方法是测试方法。

@SpringBootTest
class Boot05WebAdminApplicationTests {

    @Test
    void contextLoads() {

    }
}

 2.2.2、@DisplayName :为测试类或者测试方法设置展示名称

 测试代码:

@DisplayName("junit5功能测试")
public class Junit5Test {

    @DisplayName("测试displayname注解")
    @Test
    void testDisplayName() {
        System.out.println(1);
    }
}

 输出结果:

 2.2.3、@BeforeEach、@AfterEach、@BeforeAll、@AfterAll

 注解功能:

注解功能

@BeforeEach

表示在每个单元测试之前执行

@AfterEach

表示在每个单元测试之后执行

@BeforeAll

表示在所有单元测试之前执行

@AfterAll

表示在所有单元测试之后执行

 测试代码: 

@SpringBootTest
@DisplayName("junit5功能测试")
public class Junit5Test {

    @Autowired
    JdbcTemplate jdbcTemplate;

    /**
     * 测试前置条件
     */
    @DisplayName("测试前置条件")
    @Test
    void testAssumptions() {
        Assumptions.assumeTrue(false, "结果不足true");
        System.out.println("11111");
    }

    @DisplayName("测试displayname注解")
    @Test
    void testDisplayName() {
        System.out.println(1);
    }

    @Disabled
    @DisplayName("测试2")
    @Test
    void test2() {
        System.out.println(2);
        System.out.println(jdbcTemplate.getClass());
    }

    @BeforeEach
    void testBeforeEach() {
        System.out.println("测试就要开始。。。");
    }

    @AfterEach
    void testAfterEach() {
        System.out.println("测试就要结束。。。");
    }

    @BeforeAll
    static void testBeforeAll() {
        System.out.println("所有测试就要开始。。。");
    }

    @AfterAll
    static void testAfterAll() {
        System.out.println("所有测试已经结束。。。");
    }
}

 输出结果: 

 2.2.4、@Disabled :表示测试类或测试方法不执行

 测试代码:

    @Disabled
    @DisplayName("测试2")
    @Test
    void test2() {
        System.out.println(2);
        System.out.println(jdbcTemplate.getClass());
    }

 2.2.5、@Timeout :表示测试方法运行如果超过了指定时间将会返回错误

 测试代码:设置执行时间为500ms,超时报错

    @Test
    @Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
    void testTimeOut() throws InterruptedException {
        Thread.sleep(520);
    }

 返回结果:

 2.2.6、@RepeatedTest :表示方法可重复执行

 测试代码:

    @RepeatedTest(5)    //重复测试5次
    @DisplayName("测试3")
    @Test
    void test3() {
        System.out.println(3);
        System.out.println(jdbcTemplate.getClass());
    }

 执行结果:

 

 2.2.7、其他注解

 @ParameterizedTest :表示方法是参数化测试

 @Tag :表示单元测试类别

 @ExtendWith :为测试类或测试方法提供扩展类引用

2.3、Jnuit5 断言(assertions)

  • 检查业务逻辑返回的数据是否合理。
  • 所有的测试运行结束以后,会有一个详细的测试报告;

 2.3.1、简单断言

 1)简单断言功能:

方法

说明

assertEquals

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

assertNotEquals

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

assertSame

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

assertNotSame

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

assertTrue

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

assertFalse

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

assertNull

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

assertNotNull

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

 2)测试代码:

@SpringBootTest
@DisplayName("junit5功能测试")
public class Junit5Test {
    @DisplayName("测试简单断言")
    @Test
    void testSimpleAssertions() {
        int call = call(1,2);
        assertEquals(2, call, "业务逻辑计算失败");

        Object obj1 = new Object();
        Object obj2 = new Object();
        assertSame(obj1, obj2, "两个对象不一样");
    }

    int call (int x, int y) {
        return x + y;
    }
    }
}

 3)测试结果:

 2.3.2、数组断言

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

 测试代码:

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

 2.3.3、组合断言

  • assertAll 方法接受多个 org.junit.jupiter.api.Executable 函数式接口的实例作为要验证的断言

    • 可以通过 lambda 表达式很容易的提供这些断言

    • 前边断言失败,后续代码不会执行 

    • 组合断言,当组合中的所有断言都通过才是true,否则是false

 测试代码:

@Test
@DisplayName("assert all")
public void all() {
 assertAll("Math",
    () -> assertEquals(2, 1 + 1),
    () -> assertTrue(1 > 0)
 );
}

 2.3.4、异常断言

 JUnit5提供了Assertions.assertThrows() ,配合函数式编程就可以进行使用。

    @Test
    @DisplayName("异常断言")
    public void exceptionTest() {
        ArithmeticException exception = Assertions.assertThrows(
                //扔出断言异常
                ArithmeticException.class, () -> System.out.println(1 % 0));

    }

 2.3.5、超时断言

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

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

 2.3.6、快速失败

  • 当满足某一条件,需要退出测试程序时,可以使用快速失败方法
  • 当调用快速失败方法时,程序会退出,同时输出失败提示
@Test
@DisplayName("fail")
public void shouldFail() {
 fail("This should fail");
}

2.4、前置条件

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

 测试代码:

@SpringBootTest
@DisplayName("junit5功能测试")
public class Junit5Test {

    @Autowired
    JdbcTemplate jdbcTemplate;

    /**
     * 测试前置条件
     */
    @DisplayName("测试前置条件")
    @Test
    void testAssumptions() {
        Assumptions.assumeTrue(false, "结果不足true");
        System.out.println("11111");
    }
}

返回结果:

        1.当不满足前置条件时,后续输出代码未执行

        2.当不满足前置条件时,程序未报错退出,而是终止执行

2.5、嵌套测试

  • JUnit 5 可以通过 Java 中的内部类和@Nested 注解实现嵌套测试
  • 将相关的测试方法组织在一起。
  • 在内部类中可以使用@BeforeEach 和@AfterEach 注解,而且嵌套的层次没有限制。

 注意:

 1.外层测试方法的执行,不会驱动内层方法:

    当内层方法中有 before 或 after 等注解时,调用外层测试方法,并不会驱动其提前或最后执行

 2.内层方法的执行,可以驱动外层方法:

    当外层方法定义了某一数据结果时,内层测试方法可以直接调用该结构

 测试代码:

@DisplayName("嵌套测试")
public class TestingAstackDemo {

    Stack<Object> stack;

    @ParameterizedTest
    @DisplayName("参数化测试")
    @ValueSource(ints = {1, 2, 3, 4, 5})
    void testParamterized(int i) {
        System.out.println(i);
    }

    @ParameterizedTest
    @DisplayName("参数化方法测试")
    @MethodSource("stringProvider")
    void testParamterized2(String s) {
        System.out.println(s);
    }

    static Stream<String> stringProvider() {
        return Stream.of("apple", "banana");
    }

    @Test
    @DisplayName("is instantiated with new Stack()")
    void isInstantiatedWithNew() {
        new Stack<>();
        //在嵌套测试情况下,外层的test不能驱动内层
        //的beforeall等方法,在提前/之后运行
        //内层的test可以驱动外层
//        assertNotNull(stack);
    }

    @Nested
    @DisplayName("when new")
    class WhenNew {

        @BeforeEach
        void createNewStack() {
            stack = new Stack<>();
        }

        @Test
        @DisplayName("is empty")
        void isEmpty() {
            assertTrue(stack.isEmpty());
        }

        @Test
        @DisplayName("throws EmptyStackException when popped")
        void throwsExceptionWhenPopped() {
            assertThrows(EmptyStackException.class, stack::pop);
        }

        @Test
        @DisplayName("throws EmptyStackException when peeked")
        void throwsExceptionWhenPeeked() {
            assertThrows(EmptyStackException.class, stack::peek);
        }

        @Nested
        @DisplayName("after pushing an element")
        class AfterPushing {

            String anElement = "an element";

            @BeforeEach
            void pushAnElement() {
                stack.push(anElement);
            }

            @Test
            @DisplayName("it is no longer empty")
            void isNotEmpty() {
                assertFalse(stack.isEmpty());
            }

            @Test
            @DisplayName("returns the element when popped and is empty")
            void returnElementWhenPopped() {
                assertEquals(anElement, stack.pop());
                assertTrue(stack.isEmpty());
            }

            @Test
            @DisplayName("returns the element when peeked but remains not empty")
            void returnElementWhenPeeked() {
                assertEquals(anElement, stack.peek());
                assertFalse(stack.isEmpty());
            }
        }
    }
}

2.6、参数化测试

 1)参数化测试,可以指定测试方法的输入参数

 2)参数化测试的注解类型:

注解

功能

@ValueSource

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

@NullSource

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

@EnumSource

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

@CsvFileSource

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

@MethodSource

表示读取指定方法的返回值作为参数化测试入参(方法的返回值为流)

 测试代码:

        1)入参为基础类型

    @ParameterizedTest
    @DisplayName("参数化测试")
    @ValueSource(ints = {1, 2, 3, 4, 5})
    void testParamterized(int i) {
        System.out.println(i);
    }

         2)入参为方法返回值类型

    @ParameterizedTest
    @DisplayName("参数化方法测试")
    @MethodSource("stringProvider")
    void testParamterized2(String s) {
        System.out.println(s);
    }

    static Stream<String> stringProvider() {
        return Stream.of("apple", "banana");
    }

有关SpringBoot 单元测试——JUnit5的更多相关文章

  1. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  2. ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试? - 2

    我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当

  3. ruby - Ruby 的 Hash 在比较键时使用哪种相等性测试? - 2

    我有一个围绕一些对象的包装类,我想将这些对象用作散列中的键。包装对象和解包装对象应映射到相同的键。一个简单的例子是这样的:classAattr_reader:xdefinitialize(inner)@inner=innerenddefx;@inner.x;enddef==(other)@inner.x==other.xendenda=A.new(o)#oisjustanyobjectthatallowso.xb=A.new(o)h={a=>5}ph[a]#5ph[b]#nil,shouldbe5ph[o]#nil,shouldbe5我试过==、===、eq?并散列所有无济于事。

  4. ruby - RSpec - 使用测试替身作为 block 参数 - 2

    我有一些Ruby代码,如下所示:Something.createdo|x|x.foo=barend我想编写一个测试,它使用double代替block参数x,这样我就可以调用:x_double.should_receive(:foo).with("whatever").这可能吗? 最佳答案 specify'something'dox=doublex.should_receive(:foo=).with("whatever")Something.should_receive(:create).and_yield(x)#callthere

  5. ruby - Sinatra:运行 rspec 测试时记录噪音 - 2

    Sinatra新手;我正在运行一些rspec测试,但在日志中收到了一堆不需要的噪音。如何消除日志中过多的噪音?我仔细检查了环境是否设置为:test,这意味着记录器级别应设置为WARN而不是DEBUG。spec_helper:require"./app"require"sinatra"require"rspec"require"rack/test"require"database_cleaner"require"factory_girl"set:environment,:testFactoryGirl.definition_file_paths=%w{./factories./test/

  6. ruby-on-rails - 迷你测试错误 : "NameError: uninitialized constant" - 2

    我遵循MichaelHartl的“RubyonRails教程:学习Web开发”,并创建了检查用户名和电子邮件长度有效性的测试(名称最多50个字符,电子邮件最多255个字符)。test/helpers/application_helper_test.rb的内容是:require'test_helper'classApplicationHelperTest在运行bundleexecraketest时,所有测试都通过了,但我看到以下消息在最后被标记为错误:ERROR["test_full_title_helper",ApplicationHelperTest,1.820016791]test

  7. ruby - 即使失败也继续进行多主机测试 - 2

    我已经构建了一些serverspec代码来在多个主机上运行一组测试。问题是当任何测试失败时,测试会在当前主机停止。即使测试失败,我也希望它继续在所有主机上运行。Rakefile:namespace:specdotask:all=>hosts.map{|h|'spec:'+h.split('.')[0]}hosts.eachdo|host|begindesc"Runserverspecto#{host}"RSpec::Core::RakeTask.new(host)do|t|ENV['TARGET_HOST']=hostt.pattern="spec/cfengine3/*_spec.r

  8. ruby-on-rails - 如何使辅助方法在 Rails 集成测试中可用? - 2

    我在app/helpers/sessions_helper.rb中有一个帮助程序文件,其中包含一个方法my_preference,它返回当前登录用户的首选项。我想在集成测试中访问该方法。例如,这样我就可以在测试中使用getuser_path(my_preference)。在其他帖子中,我读到这可以通过在测试文件中包含requiresessions_helper来实现,但我仍然收到错误NameError:undefinedlocalvariableormethod'my_preference'.我做错了什么?require'test_helper'require'sessions_hel

  9. ruby-on-rails - Cucumber 是否只是 rspec 的包装器以帮助将测试组织成功能? - 2

    只是想确保我理解了事情。据我目前收集到的信息,Cucumber只是一个“包装器”,或者是一种通过将事物分类为功能和步骤来组织测试的好方法,其中实际的单元测试处于步骤阶段。它允许您根据事物的工作方式组织您的测试。对吗? 最佳答案 有点。它是一种组织测试的方式,但不仅如此。它的行为就像最初的Rails集成测试一样,但更易于使用。这里最大的好处是您的session在整个Scenario中保持透明。关于Cucumber的另一件事是您(应该)从使用您的代码的浏览器或客户端的角度进行测试。如果您愿意,您可以使用步骤来构建对象和设置状态,但通常您

  10. ruby-on-rails - 如何调试 cucumber 测试? - 2

    我有:When/^(?:|I)follow"([^"]*)"(?:within"([^"]*)")?$/do|link,selector|with_scope(selector)doclick_link(link)endend我打电话的地方:Background:GivenIamanexistingadminuserWhenIfollow"CLIENTS"我的HTML是这样的:CLIENTS我一直收到这个错误:.F-.F--U-----U(::)failedsteps(::)nolinkwithtitle,idortext'CLIENTS'found(Capybara::Element

随机推荐