欢迎访问我的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/blog_demos 该项目在GitHub上的主页
git仓库地址(https) https://github.com/zq2599/blog_demos.git 该项目源码的仓库地址,https协议
git仓库地址(ssh) git@github.com:zq2599/blog_demos.git 该项目源码的仓库地址,ssh协议
  1. 这个git项目中有多个文件夹,本章的应用在junitpractice文件夹下,如下图红框所示:

  1. junitpractice是父子结构的工程,本篇的代码在advanced子工程中,如下图:

版本设置

  • 《JUnit5学习》系列的代码都在用SpringBoot:2.3.4.RELEASE框架,间接依赖的JUnit版本是5.6.2;
  • 本文有两个特性要求JUnit版本达到5.7或者更高,它们是测试方法展现名称生成器和动态生成测试方法;
  • 对于使用SpringBoot:2.3.4.RELEASE框架的工程,如果要指定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. 如下图,刷新可见已经用上了5.7.0版本:

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

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

  1. 把Display Name Generators翻译成测试方法展现名称生成器,可能刷新了读者们对本文作者英文水平的认知,请您多包含...
  2. 先回顾一下如何指定测试方法的展现名称,如果测试方法使用了@DisplayName,在展示单元测试执行结果时,就会显示@DisplayName指定的字符串,如下图所示:



3. 除了用@DisplayName指定展示名称,JUnit5还提供了一种自动生成展示名称的功能:@DisplayNameGeneration,来看看它是如何生成展示名称的;

4. 演示代码如下所示,当@DisplayNameGeneration的value设置为ReplaceUnderscores时,会把方法名的所有下划线替换为空格:

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. 执行结果如下图,方法if_it_is_zero展示出的名字为if it is zero:



6. 在上述替换方式的基础上,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. 重复测试就是指定某个测试方法反复执行多次,演示代码如下,可见@Test已被@RepeatedTest(5)取代,数字5表示重复执行5次:
    @Order(1)
@DisplayName("重复测试")
@RepeatedTest(5)
void repeatTest(TestInfo testInfo) {
log.info("测试方法 [{}]", testInfo.getTestMethod().get().getName());
}
  1. 执行结果如下图:



3. 在测试方法执行时,如果想了解当前是第几次执行,以及总共有多少次,只要给测试方法增加RepetitionInfo类型的入参即可,演示代码如下,可见RepetitionInfo提供的API可以得到总数和当前次数:

    @Order(2)
@DisplayName("重复测试,从入参获取执行情况")
@RepeatedTest(5)
void repeatWithParamTest(TestInfo testInfo, RepetitionInfo repetitionInfo) {
log.info("测试方法 [{}],当前第[{}]次,共[{}]次",
testInfo.getTestMethod().get().getName(),
repetitionInfo.getCurrentRepetition(),
repetitionInfo.getTotalRepetitions());
}
  1. 上述代码执行结果如下:



5. 在上图的左下角可见,重复执行的结果被展示为"repetition X of X"这样的内容,其实这部分信息是可以定制的,就是RepeatedTest注解的name属性,演示代码如下,可见currentRepetition和totalRepetitions是占位符,在真正展示的时候会被分别替换成当前值和总次数:

    @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注解,这样就会以内部类为单位执行测试和展现结果,如下图所示:



3. 嵌套测试的演示代码如下:

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. 之前咱们写的测试方法,主要是用@Test修饰,这些方法的特点就是在编译阶段就已经明确了,在运行阶段也已经固定;
  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
@Slf4j
class 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的配置文件,如下图,在test文件夹上点击鼠标右键,在弹出的菜单选择"New"->"Directory":

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

  1. 在新增的resources目录中新建文件junit-platform.properties,内容如下,每个配置项都有详细的说明:
# 并行开关true/false
junit.jupiter.execution.parallel.enabled=true
# 方法级多线程开关 same_thread/concurrent
junit.jupiter.execution.parallel.mode.default = same_thread
# 类级多线程开关 same_thread/concurrent
junit.jupiter.execution.parallel.mode.classes.default = same_thread # 并发策略有以下三种可选:
# fixed:固定线程数,此时还要通过junit.jupiter.execution.parallel.config.fixed.parallelism指定线程数
# dynamic:表示根据处理器和核数计算线程数
# custom:自定义并发策略,通过这个配置来指定:junit.jupiter.execution.parallel.config.custom.class
junit.jupiter.execution.parallel.config.strategy = fixed # 并发线程数,该配置项只有当并发策略为fixed的时候才有用
junit.jupiter.execution.parallel.config.fixed.parallelism = 5
  1. 由于实践的是同一个类同一个方法多次执行的并发,因此上述配置中,类级多线程开关和方法级多线程开关都选择了"同一个线程",也就是说不需要并发执行多个类或者多个方法,请您根据自己的需求自行调整;
  2. 关于并发策略,这里选择的是动态调整,我这里是i5-8400处理器,拥有六核心六线程,稍后咱们看看执行效果与这个硬件配置是否有关系;
  3. 接下来编写测试代码,先写一个单线程执行的,可见@Execution的值为SAME_THREAD,限制了重复测试时在同一个线程内顺序执行:
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的值为CONCURRENT:
    @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可见十次测试方法是在五个线程中执行的:



11. 最后是参数化测试的演示,也可以设置为多线程并行执行:

    @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

JUnit5学习之八:综合进阶(终篇)的更多相关文章

  1. jackson学习之十(终篇):springboot整合(配置类)

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  2. JUnit5学习之一:基本操作

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  3. JUnit5学习之二:Assumptions类

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  4. JUnit5学习之七:参数化测试(Parameterized Tests)进阶

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  5. JMeter学习-011-JMeter 后置处理器实例之 - 正则表达式提取器(三)多参数获取进阶引用篇

    前两篇文章分表讲述了 后置处理器 - 正则表达式提取器概述及简单实例.多参数获取,相应博文敬请参阅 简单实例.多参数获取. 此文主要讲述如何引用正则表达式提取器获取的数据信息.其实,正则表达式提取器获 ...

  6. Django学习笔记(进阶篇)

    Django学习笔记(进阶篇):http://www.cnblogs.com/wupeiqi/articles/5246483.html

  7. Oracle RMAN 学习:演练进阶篇

    Oracle RMAN 学习:演练进阶篇 5 Rman备份演练进阶篇 5.1 是否选择增量备份 Backup命令生成的备份集中只备份了那些使用了的数据块,备份集实际大小已经较目标数据库的数据文件小了很 ...

  8. disruptor笔记之八:知识点补充(终篇)

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  9. JUnit5学习之三:Assertions类

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

随机推荐

  1. Codeforces Round #658 (Div. 2)【ABC2】

    做完前四题还有一个半小时... 比赛链接:https://codeforces.com/contest/1382 A. Common Subsequence 题意 给出两个数组,找出二者最短的公共子序 ...

  2. hdu5497 Inversion

    Problem Description You have a sequence {a1,a2,...,an} and you can delete a contiguous subsequence o ...

  3. Codeforces #624 div3 C

    You want to perform the combo on your opponent in one popular fighting game. The combo is the string ...

  4. poj2923 Relocation

    Description Emma and Eric are moving to their new house they bought after returning from their honey ...

  5. Codeforces Round #643 (Div. 2) E. Restorer Distance (贪心,三分)

    题意:给你\(n\)个数,每次可以使某个数++,--,或使某个数--另一个++,分别消耗\(a,r,m\).求使所有数相同最少的消耗. 题解:因为答案不是单调的,所以不能二分,但不难发现,答案只有一个 ...

  6. 13. 从0学ARM-Cortex-A9 RTC裸机程序编写

    一.RTC RTC(Real-Time Clock) 实时时钟. RTC是集成电路,通常称为时钟芯片.在一个嵌入式系统中,通常采用RTC来提供可靠的系统时间,包括时分秒和年月日等,而且要求在系统处于关 ...

  7. 深入理解gradle中的task

    目录 简介 定义task tasks 集合类 Task 之间的依赖 定义task之间的顺序 给task一些描述 task的条件执行 task rule Finalizer tasks 总结 深入理解g ...

  8. MBP 屏幕分辨率 All In One

    MBP 屏幕分辨率 All In One screen size bug https://stackoverflow.com/questions/65462643/how-to-get-the-rea ...

  9. write a node cli tools, step by step

    write a node cli tools, step by step how to write a node cli tools node cli tools, step by step, nod ...

  10. js & h5 & app & object storage

    js & h5 & app & object storage API https://developer.mozilla.org/en-US/docs/Web/API Stor ...