乐趣区

关于云计算:JUnit5学习之八综合进阶终篇

欢送拜访我的 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
@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 的配置文件,如下图,在 <font color=”blue”>test</font> 文件夹上点击鼠标右键,在弹出的菜单抉择 ”New”->”Directory”:

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

  1. 在新增的 resources 目录中新建文件 <font color=”blue”>junit-platform.properties</font>,内容如下,每个配置项都有具体的阐明:
# 并行开关 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. 对于并发策略,这里抉择的是 <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

退出移动版