欢送拜访我的GitHub

https://github.com/zq2599/blog_demos

内容:所有原创文章分类汇总及配套源码,波及Java、Docker、Kubernetes、DevOPS等;

对于《JUnit5学习》系列

《JUnit5学习》系列旨在通过实战晋升SpringBoot环境下的单元测试技能,一共八篇文章,链接如下:

  1. 基本操作
  2. Assumptions类
  3. Assertions类
  4. 按条件执行
  5. 标签(Tag)和自定义注解
  6. 参数化测试(Parameterized Tests)根底
  7. 参数化测试(Parameterized Tests)进阶
  8. 综合进阶(终篇)

本篇概览

  • 本文是《JUnit5学习》系列的终篇,将JUnit5提供的一些高级个性以实战的模式展示进去;
  • JUnit5的个性十分多,《JUnit5学习》系列也只是将罕用局部写进去,未能笼罩全副;
  • 本文由以下章节组成:
  1. 版本设置
  2. 测试方法展示名称生成器
  3. 反复测试
  4. 嵌套
  5. 动静测试(Dynamic Tests)
  6. 多线程并发执行测试方法

源码下载

  1. 如果您不想编码,能够在GitHub下载所有源码,地址和链接信息如下表所示:
名称链接备注
我的项目主页https://github.com/zq2599/blo...该我的项目在GitHub上的主页
git仓库地址(https)https://github.com/zq2599/blo...该我的项目源码的仓库地址,https协定
git仓库地址(ssh)git@github.com:zq2599/blog_demos.git该我的项目源码的仓库地址,ssh协定
  1. 这个git我的项目中有多个文件夹,本章的利用在<font color="blue">junitpractice</font>文件夹下,如下图红框所示:

  1. <font color="blue">junitpractice</font>是父子构造的工程,本篇的代码在<font color="red">advanced</font>子工程中,如下图:

版本设置

  • 《JUnit5学习》系列的代码都在用<font color="blue">SpringBoot:2.3.4.RELEASE</font>框架,间接依赖的JUnit版本是<font color="red">5.6.2</font>;
  • 本文有两个个性要求JUnit版本达到<font color="red">5.7或者更高</font>,它们是<font color="blue">测试方法展示名称生成器</font>和<font color="blue">动静生成测试方法</font>;
  • 对于应用<font color="blue">SpringBoot:2.3.4.RELEASE</font>框架的工程,如果要指定JUnit版本,须要做以下三步操作:
  1. dependencyManagement节点增加junit-bom,并指定版本号:
<dependencyManagement>  <dependencies>    <dependency>      <groupId>org.junit</groupId>      <artifactId>junit-bom</artifactId>      <version>5.7.0</version>      <type>pom</type>      <scope>import</scope>    </dependency>  </dependencies></dependencyManagement>
  1. 排除spring-boot-starter-test和junit-jupiter的间接依赖关系:
<dependency>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-test</artifactId>  <scope>test</scope>  <exclusions>    <exclusion>      <groupId>org.junit.jupiter</groupId>      <artifactId>junit-jupiter</artifactId>    </exclusion>  </exclusions></dependency>
  1. 增加junit-jupiter依赖,此时会应用dependencyManagement中指定的版本号:
<dependency>  <groupId>org.junit.jupiter</groupId>  <artifactId>junit-jupiter</artifactId>  <scope>test</scope></dependency>
  1. 如下图,刷新可见曾经用上了<font color="blue">5.7.0</font>版本:

  • 版本问题解决了,接下来正式进入进阶实战;

测试方法展示名称生成器(Display Name Generators)

  1. 把<font color="red">Display Name Generators</font>翻译成<font color="blue">测试方法展示名称生成器</font>,可能刷新了读者们对本文作者英文程度的认知,请您多蕴含...
  2. 先回顾一下如何指定测试方法的展示名称,如果测试方法应用了@DisplayName,在展现单元测试执行后果时,就会显示@DisplayName指定的字符串,如下图所示:

  1. 除了用@DisplayName指定展现名称,JUnit5还提供了一种主动生成展现名称的性能:@DisplayNameGeneration,来看看它是如何生成展现名称的;
  2. 演示代码如下所示,当@DisplayNameGeneration的value设置为<font color="blue">ReplaceUnderscores</font>时,会把办法名的所有下划线替换为空格:
package com.bolingcavalry.advanced.service.impl;import org.junit.jupiter.api.DisplayNameGeneration;import org.junit.jupiter.api.DisplayNameGenerator;import org.junit.jupiter.api.Test;import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)public class ReplaceUnderscoresTest {    @Test    void if_it_is_zero() {    }}
  1. 执行后果如下图,办法<font color="blue">if_it_is_zero</font>展现出的名字为<font color="red">if it is zero</font>:

  1. 在上述替换形式的根底上,JUnit5还提供了另一种生成展现名称的办法:测试类名+连接符+测试方法名,并且类名和办法名的下划线都会被替换成空格,演示代码如下,应用了注解@IndicativeSentencesGeneration,其separator属性就是类名和办法名之间的连接符:
package com.bolingcavalry.advanced.service.impl;import org.junit.jupiter.api.DisplayNameGenerator;import org.junit.jupiter.api.IndicativeSentencesGeneration;import org.junit.jupiter.api.Test;import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest@IndicativeSentencesGeneration(separator = ",测试方法:", generator = DisplayNameGenerator.ReplaceUnderscores.class)public class IndicativeSentences_Test {    @Test    void if_it_is_one_of_the_following_years() {    }}
  1. 执行后果如下:

反复测试(Repeated Tests)

  1. 反复测试就是指定某个测试方法重复执行屡次,演示代码如下,可见<font color="blue">@Test</font>已被<font color="red">@RepeatedTest(5)</font>取代,数字5示意反复执行5次:
    @Order(1)    @DisplayName("反复测试")    @RepeatedTest(5)    void repeatTest(TestInfo testInfo) {        log.info("测试方法 [{}]", testInfo.getTestMethod().get().getName());    }
  1. 执行后果如下图:

  1. 在测试方法执行时,如果想理解以后是第几次执行,以及总共有多少次,只有给测试方法减少<font color="blue">RepetitionInfo</font>类型的入参即可,演示代码如下,可见RepetitionInfo提供的API能够失去总数和以后次数:
    @Order(2)    @DisplayName("反复测试,从入参获取执行状况")    @RepeatedTest(5)    void repeatWithParamTest(TestInfo testInfo, RepetitionInfo repetitionInfo) {        log.info("测试方法 [{}],以后第[{}]次,共[{}]次",                testInfo.getTestMethod().get().getName(),                repetitionInfo.getCurrentRepetition(),                repetitionInfo.getTotalRepetitions());    }
  1. 上述代码执行后果如下:

  1. 在上图的左下角可见,反复执行的后果被展现为"repetition X of X"这样的内容,其实这部分信息是能够定制的,就是RepeatedTest注解的<font color="red">name</font>属性,演示代码如下,可见<font color="blue">currentRepetition</font>和<font color="blue">totalRepetitions</font>是占位符,在真正展现的时候会被别离替换成以后值和总次数:
    @Order(3)    @DisplayName("反复测试,应用定制名称")    @RepeatedTest(value = 5, name="完成度:{currentRepetition}/{totalRepetitions}")    void repeatWithCustomDisplayNameTest(TestInfo testInfo, RepetitionInfo repetitionInfo) {        log.info("测试方法 [{}],以后第[{}]次,共[{}]次",                testInfo.getTestMethod().get().getName(),                repetitionInfo.getCurrentRepetition(),                repetitionInfo.getTotalRepetitions());    }
  1. 上述代码执行后果如下:

嵌套测试(Nested Tests)

  1. 如果一个测试类中有很多测试方法(如增删改查,每种操作都有多个测试方法),那么不论是治理还是后果展示都会显得比较复杂,此时嵌套测试(Nested Tests)就派上用场了;
  2. 嵌套测试(Nested Tests)性能就是在测试类中创立一些外部类,以增删改查为例,将所有测试查找的办法放入一个外部类,将所有测试删除的办法放入另一个外部类,再给每个外部类减少@Nested注解,这样就会以内部类为单位执行测试和展示后果,如下图所示:

  1. 嵌套测试的演示代码如下:
package com.bolingcavalry.advanced.service.impl;import lombok.extern.slf4j.Slf4j;import org.junit.jupiter.api.DisplayName;import org.junit.jupiter.api.Nested;import org.junit.jupiter.api.Test;import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest@Slf4j@DisplayName("嵌套测试演示")public class NestedTest {    @Nested    @DisplayName("查找服务相干的测试")    class FindService {        @Test        void findByIdTest() {}        @Test        void findByNameTest() {}    }    @Nested    @DisplayName("删除服务相干的测试")    class DeleteService {        @Test        void deleteByIdTest() {}        @Test        void deleteByNameTest() {}    }}
  1. 上述代码执行后果如下,可见从代码治理再到执行和后果展现,都被分组治理了:

动静测试(Dynamic Tests)

  1. 之前咱们写的测试方法,次要是用<font color="blue">@Test</font>润饰,这些办法的特点就是在编译阶段就曾经明确了,在运行阶段也曾经固定;
  2. JUnit5推出了另一种类型的测试方法:动静测试(Dynamic Tests),首先,测试方法是能够在运行期间被生产进去的,生产它们的中央,就是被@TestFactory润饰的办法,等到测试方法被生产进去后再像传统的测试方法那样被执行和后果展现;
  3. 上面是演示代码,testFactoryTest办法被@TestFactory润饰,返回值是Iterable类型,外面是多个DynamicTest实例,每个DynamicTest实例代表一个测试方法,因而,整个DynamicDemoTest类中有多少个测试方法,在编译阶段是不能确定的,只有在运行阶段执行了testFactoryTest办法后,能力依据返回值确定下来:
package com.bolingcavalry.advanced.service.impl;import lombok.extern.slf4j.Slf4j;import org.junit.jupiter.api.DynamicTest;import org.junit.jupiter.api.TestFactory;import org.springframework.boot.test.context.SpringBootTest;import java.util.Arrays;import static org.junit.jupiter.api.DynamicTest.dynamicTest;@SpringBootTest@Slf4jclass DynamicDemoTest {    @TestFactory    Iterable<org.junit.jupiter.api.DynamicTest> testFactoryTest() {        DynamicTest firstTest = dynamicTest(            "一号动静测试用例",            () -> {                log.info("一号用例,这里编写单元测试逻辑代码");            }        );        DynamicTest secondTest = dynamicTest(                "二号动静测试用例",                () -> {                    log.info("二号用例,这里编写单元测试逻辑代码");                }        );        return Arrays.asList(firstTest, secondTest);    }}
  1. 上述代码的执行后果如下,可见每个DynamicTest实例就相当于以前的一个@Test润饰的办法,会被执行和统计:

多线程并发执行(Parallel Execution)的介绍

  • 《JUnit5学习》系列的最初,咱们来看一个既容易了解又实用的个性:多线程并发执行(Parallel Execution)
  • JUnit5中的并发执行测试能够分为以下三种场景:
  1. 多个测试类,它们各自的测试方法同时执行;
  2. 一个测试类,外面的多个测试方法同时执行;
  3. 一个测试类,外面的一个测试方法,在反复测试(Repeated Tests)或者参数化测试(Parameterized Tests)的时候,这个测试方法被多个线程同时执行;

多线程并发执行(Parallel Execution)实战

  1. 后面介绍了多线程并发执行有三种场景,文章篇幅所限就不一一编码实战了,就抉择第三种场景来实际吧,即:一个测试类外面的一个测试方法,在反复测试时多线程并发执行,至于其余两种场景如何设置,接下来的文中也会讲清楚,您自行实际即可;
  2. 首先是创立JUnit5的配置文件,如下图,在<font color="blue">test</font>文件夹上点击鼠标右键,在弹出的菜单抉择"New"->"Directory":

  1. 弹出的窗口如下图,双击红框地位的"resources",即可新建resources目录:

  1. 在新增的resources目录中新建文件<font color="blue">junit-platform.properties</font>,内容如下,每个配置项都有具体的阐明:
# 并行开关true/falsejunit.jupiter.execution.parallel.enabled=true# 办法级多线程开关 same_thread/concurrentjunit.jupiter.execution.parallel.mode.default = same_thread# 类级多线程开关 same_thread/concurrentjunit.jupiter.execution.parallel.mode.classes.default = same_thread# 并发策略有以下三种可选:# fixed:固定线程数,此时还要通过junit.jupiter.execution.parallel.config.fixed.parallelism指定线程数# dynamic:示意依据处理器和核数计算线程数# custom:自定义并发策略,通过这个配置来指定:junit.jupiter.execution.parallel.config.custom.classjunit.jupiter.execution.parallel.config.strategy = fixed# 并发线程数,该配置项只有当并发策略为fixed的时候才有用junit.jupiter.execution.parallel.config.fixed.parallelism = 5
  1. 因为实际的是同一个类同一个办法屡次执行的并发,因而上述配置中,类级多线程开关和办法级多线程开关都抉择了"同一个线程",也就是说不须要并发执行多个类或者多个办法,请您依据本人的需要自行调整;
  2. 对于并发策略,这里抉择的是<font color="blue">动静调整</font>,我这里是<font color="blue">i5-8400</font>处理器,领有六外围六线程,稍后咱们看看执行成果与这个硬件配置是否有关系;
  3. 接下来编写测试代码,先写一个单线程执行的,可见@Execution的值为<font color="red">SAME_THREAD</font>,限度了反复测试时在同一个线程内程序执行:
package com.bolingcavalry.advanced.service.impl;import lombok.extern.slf4j.Slf4j;import org.junit.jupiter.api.*;import org.junit.jupiter.api.parallel.Execution;import org.junit.jupiter.api.parallel.ExecutionMode;import org.junit.jupiter.params.ParameterizedTest;import org.junit.jupiter.params.provider.ValueSource;import org.springframework.boot.test.context.SpringBootTest;import static org.junit.jupiter.api.Assertions.assertTrue;@SpringBootTest@Slf4j@TestMethodOrder(MethodOrderer.OrderAnnotation.class)class ParallelExecutionTest {    @Order(1)    @Execution(ExecutionMode.SAME_THREAD)    @DisplayName("单线程执行10次")    @RepeatedTest(value = 10, name="完成度:{currentRepetition}/{totalRepetitions}")    void sameThreadTest(TestInfo testInfo, RepetitionInfo repetitionInfo) {        log.info("测试方法 [{}],以后第[{}]次,共[{}]次",                testInfo.getTestMethod().get().getName(),                repetitionInfo.getCurrentRepetition(),                repetitionInfo.getTotalRepetitions());    }}
  1. 执行后果如下,可见的确是单线程:

  1. 反复测试时并发执行的代码如下,@Execution的值为<font color="red">CONCURRENT</font>:
    @Order(2)    @Execution(ExecutionMode.CONCURRENT)    @DisplayName("多线程执行10次")    @RepeatedTest(value = 10, name="完成度:{currentRepetition}/{totalRepetitions}")    void concurrentTest(TestInfo testInfo, RepetitionInfo repetitionInfo) {        log.info("测试方法 [{}],以后第[{}]次,共[{}]次",                testInfo.getTestMethod().get().getName(),                repetitionInfo.getCurrentRepetition(),                repetitionInfo.getTotalRepetitions());    }
  1. 执行后果如下,从红框1可见程序曾经乱了,从红框2可见十次测试方法是在五个线程中执行的:

  1. 最初是参数化测试的演示,也能够设置为多线程并行执行:
    @Order(3)    @Execution(ExecutionMode.CONCURRENT)    @DisplayName("多个int型入参")    @ParameterizedTest    @ValueSource(ints = { 1,2,3,4,5,6,7,8,9,0 })    void intsTest(int candidate) {        log.info("ints [{}]", candidate);    }
  1. 执行后果如下图,可见也是5个线程并行执行的:

结束语

至此,《JUnit5学习》系列曾经全副实现,感谢您的急躁浏览,心愿这个原创系列可能带给您一些有用的信息,为您的单元测试提供一些参考,如果发现文章有谬误,期待您能指导一二;

你不孤独,欣宸原创一路相伴

  1. Java系列
  2. Spring系列
  3. Docker系列
  4. kubernetes系列
  5. 数据库+中间件系列
  6. DevOps系列

欢送关注公众号:程序员欣宸

微信搜寻「程序员欣宸」,我是欣宸,期待与您一起畅游Java世界...
https://github.com/zq2599/blog_demos