JUnit5学习之五:标签(Tag)和自定义注解
欢迎访问我的GitHub
https://github.com/zq2599/blog_demos
内容:所有原创文章分类汇总及配套源码,涉及Java、Docker、Kubernetes、DevOPS等;
关于《JUnit5学习》系列
《JUnit5学习》系列旨在通过实战提升SpringBoot环境下的单元测试技能,一共八篇文章,链接如下:
- 基本操作
- Assumptions类
- Assertions类
- 按条件执行
- 标签(Tag)和自定义注解
- 参数化测试(Parameterized Tests)基础
- 参数化测试(Parameterized Tests)进阶
- 综合进阶(终篇)
本篇概览
本文是《JUnit5学习》系列的第五篇,一起来学习JUnit5的标签(Tag)功能,设想一个工程中的有很多测试类和测试方法,有的场景只需执行其中一部分测试方法,如何实现呢?此时Junit的标签功能就派上用场了,咱们可以按需要给测试类或者方法打标签,在执行单元测试时按照标签进行过滤,学完了标签再来了解JUnit5对自定义注解的支持情况,本篇大纲如下:
- 设置标签
- 在IDEA中做标签过滤
- 用maven命令时做标签过滤
- 用surefire插件时做标签过滤
- 标签表达式
- 自定义注解
- 更加简化的自定义注解
- 标签命名规范
源码下载
- 如果您不想编码,可以在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协议 |
- 这个git项目中有多个文件夹,本章的应用在junitpractice文件夹下,如下图红框所示:

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

设置标签
- 在父工程junitpractice里新建名为tag的子工程,今天的单元测试代码都写在这个tag工程中;
- 一共写两个测试类,第一个FirstTest.java如下,可见类上有Tag注解,值为first,另外每个方法上都有Tag注解,其中first1Test方法有两个Tag注解:
package com.bolingcavalry.tag.service.impl;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import static org.junit.jupiter.api.Assertions.assertEquals;
@SpringBootTest
@Slf4j
@Tag("first")
public class FirstTest {
@Test
@Tag("easy")
@Tag("important")
@DisplayName("first-1")
void first1Test() {
log.info("first1Test");
assertEquals(2, Math.addExact(1, 1));
}
@Test
@Tag("easy")
@DisplayName("first-2")
void first2Test() {
log.info("first2Test");
assertEquals(2, Math.addExact(1, 1));
}
@Test
@Tag("hard")
@DisplayName("first-3")
void first3Test() {
log.info("first3Test");
assertEquals(2, Math.addExact(1, 1));
}
}
- 第二个测试类SecondTest.java,也是类和方法都有Tag注解:
package com.bolingcavalry.tag.service.impl;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import static org.junit.jupiter.api.Assertions.assertEquals;
@SpringBootTest
@Slf4j
@Tag("second")
public class SecondTest {
@Test
@Tag("easy")
@DisplayName("second-1")
void second1Test() {
log.info("second1Test");
assertEquals(2, Math.addExact(1, 1));
}
@Test
@Tag("easy")
@DisplayName("second-2")
void second2Test() {
log.info("second2Test");
assertEquals(2, Math.addExact(1, 1));
}
@Test
@Tag("hard")
@Tag("important")
@DisplayName("second-3")
void second3Test() {
log.info("second3Test");
assertEquals(2, Math.addExact(1, 1));
}
}
- 以上就是打好了标签的测试类和测试方法了,接下来看看如何通过这些标签对测试方法进行过滤,执行单元测试有三种常用方式,咱们挨个尝试每种方式如何用标签过滤;
在IDEA中做标签过滤
- 如下图所示,点击红框中的Edit Configurations...:

2. 如下图红框,在弹出的窗口上新增一个JUnit配置:

3. 接下来的操作如下图所示,Test kind选择Tags,就会按照标签过滤测试方法,Tag expression里面填写过滤规则,后面会详细讲解这个规则,这里先填个已存在的标签important:

4. 创建好JUnit配置后,执行下图红框中的操作即可执行单元测试:

- 执行结果如下,所有打了important标签的测试方法被执行:

用maven命令时做标签过滤
- 前面试过IDEA上按标签过滤测试方法,其实用maven命令执行单元测试的时候也能按标签来过滤,接下来试试;
- 在父工程junitpractice的pom.xml所在目录下,执行以下命令,即可开始单元测试,并且只执行带有标签的方法:
mvn clean test -Dgroups="important"
- 执行完毕后结果如下:

4. 翻看日志,可见只有打了important标签的测试方法被执行了,如下图红框所示:

- 再看看其他子工程的执行情况,用前一篇文章里的conditional为例,可见没有任何测试方法被执行,如下图红框所示:

再去看看surefire插件给出的测试报告,报告文件在junitpractice\tag\target\surefire-reports目录下,下图红框中的文件就是测试报告:

打开上图红框中的一个文件,如下图红框,可见只有打了important标签的测试方法被执行了:

- 以上就是maven命令执行单元测试时使用标签过滤的方法,接下来试试在使用maven-surefire-plugin插件时如何通过做标签过滤
用surefire插件时做标签过滤
- surefire是个测试引擎(TestEngine),以maven插件的方式来使用,打开tag子工程的pom.xml文件,将build节点配置成以下形式,可见groups就是标签过滤节点,另外excludedGroups节点制定的hard标签的测试方法不会执行:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<configuration>
<!--要执行的标签-->
<groups>important</groups>
<!--不要执行的标签-->
<excludedGroups>hard</excludedGroups>
</configuration>
</plugin>
</plugins>
</build>
- 在tag子工程的pom.xml所在目录,执行命令mvn clean test即可开始单元测试,结果如下,可见打了important标签的first1Test被执行,而second3Test方法尽管有important标签,但是由于其hard标签已经被设置为不执行,因此second3Test没有被执行:

标签表达式
- 前面咱们用三种方法执行了单元测试,每次都是用important标签过滤,其实除了指定标签,JUnit还支持更复杂的标签过滤,即标签表达式
- 所谓标签表达式,就是用"非"、"与"、"或"这三种操作符将更多的标签连接起来,实现更复杂的过滤逻辑;
- 上述三种操作符的定义和用法如下表:
| 操作符 | 作用 | 举例 | 举例说明 |
|---|---|---|---|
| & | 与 | important & easy | 既有important,又有easy标签, 在本文是first1Test |
| ! | 非 | important & !easy | 有important,同时又没有easy标签, 在本文是second3Test |
| | | 或 | important | hard | 有important标签的,再加上有hard标签的, 在本文是first1Test、first3Test、second3Test |
- 试试标签表达式的效果,如下图红框,修改前面创建好的IDEA配置,从之前的important改为important | hard:

5. 再次执行这个配置,结果如下图红框所示,只有这三个方法被执行:first1Test、first3Test、second3Test,可见标签表达式生效了:

6. 在maven命令和surefire插件中使用标签表达式的操作就不在文中执行了,请您自行验证;
自定义注解
- JUnit支持自定义注解,先回顾之前的代码,看咱们是如何给方法打标签的,以first3Test方法为例:
@Test
@Tag("hard")
@DisplayName("first-3")
void first3Test() {
log.info("first3Test");
assertEquals(2, Math.addExact(1, 1));
}
- 接下来咱们创建一个注解,将@Tag("hard")替换掉,新注解的源码如下,可见仅是一个普通的注解定义:
package com.bolingcavalry.tag.service.impl;
import org.junit.jupiter.api.Tag;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Tag("hard")
public @interface Hard {
}
- 修改first3Test方法的注解,去掉@Tag("hard"),改为@Hard:
@Test
@Hard
@DisplayName("first-3")
void first3Test() {
log.info("first3Test");
assertEquals(2, Math.addExact(1, 1));
}
- 执行前面创建的tag-important配置,可见hard标签的过滤依旧有效:

更加简化的自定义注解
- 上述Hard注解取代了@Tag("hard"),其实还可以更进一步对已有注解做简化,下面是个新的注解:HardTest.java,和Hard.java相比,多了个@Test,作用是集成了Test注解的能力
package com.bolingcavalry.tag.service.impl;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Tag("hard")
@Test
public @interface HardTest {
}
- 于是,first3Test方法的注解可以改成下面的效果,可见Test和Tag注解都去掉了:
@HardTest
@DisplayName("first-3")
void first3Test() {
log.info("first3Test");
assertEquals(2, Math.addExact(1, 1));
}
- 执行前面创建的tag-important配置,可见hard标签的过滤依旧有效:

标签命名规范
最后一起来看看给标签取名时有哪些要注意的地方:
- 标签名左右两侧的空格是无效的,执行测试的时候会做trim处理,例如下面这个标签会被当作hard来过滤:

2. 标签名不能有这六个符号, ( ) & | !
- 至此,JUnit5的标签过滤和自定义注解功能都学习完成了,有了这些能力,咱们可以更加灵活和随心所欲的应付不同的场景和需求;
你不孤单,欣宸原创一路相伴
欢迎关注公众号:程序员欣宸
微信搜索「程序员欣宸」,我是欣宸,期待与您一同畅游Java世界...
https://github.com/zq2599/blog_demos
JUnit5学习之五:标签(Tag)和自定义注解的更多相关文章
- JUnit5学习之一:基本操作
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- JUnit5学习之二:Assumptions类
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- JUnit5学习之三:Assertions类
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- JUnit5学习之四:按条件执行
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- JUnit5学习之六:参数化测试(Parameterized Tests)基础
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- JUnit5学习之七:参数化测试(Parameterized Tests)进阶
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- JUnit5学习之八:综合进阶(终篇)
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- AOP自定义注解鉴权
刚出来工作那会或者在学校的时候,经常听到说AOP(面向对象编程,熟称切面)的用途是日志.鉴权等.但是那会不会,后面学会了,又没有写博客记录,今天写给大伙,希望能帮到大家 一.学习目标:利用AOP+自定 ...
- [原创]java WEB学习笔记42:带标签体的自定义标签,带父标签的自定义标签,el中自定义函数,自定义标签的小结
本博客为原创:综合 尚硅谷(http://www.atguigu.com)的系统教程(深表感谢)和 网络上的现有资源(博客,文档,图书等),资源的出处我会标明 本博客的目的:①总结自己的学习过程,相当 ...
随机推荐
- Codeforces 1364C - Ehab and Prefix MEXs
题意:给1e5的数组a 保证 ai <= ai+1 ai<=i 求一个一样长的数组b 使得mex(b1,b2···bi) = ai QAQ:不知道为啥这1600分的题比赛时出不了 啊啊 ...
- Pokémon Army (easy version) CodeForces - 1420C1 dp
题意: 给你一个长度为n个序列v,你需要从中找一个子序列.这个子序列的值等于:子序列中奇数下标的值-偶数下标的值 你需要使得这个值尽可能大,让你输出这个最大值 题解: dp[i][0]表示:在原序列从 ...
- 【poj 1988】Cube Stacking(图论--带权并查集)
题意:有N个方块,M个操作{"C x":查询方块x上的方块数:"M x y":移动方块x所在的整个方块堆到方块y所在的整个方块堆之上}.输出相应的答案. 解法: ...
- Codeforces Round#630 div2 A~C题解
...
- DNS 是什么?如何运作的?
前言 我们在上一篇说到,IP 地址的发明把我们纷乱复杂的网络设备整齐划一地统一在了同一个网络中. 但是类似于 192.168.1.0 这样的地址并不便于人类记忆,于是发明了 域名(Domain Nam ...
- 国产网络测试仪MiniSMB - 如何3秒内创建出16,000条IP递增流
国产网络测试仪MiniSMB(www.minismb.com)是复刻smartbits的IP网络性能测试工具,是一款专门用于测试智能路由器,网络交换机的性能和稳定性的软硬件相结合的工具.可以通过此以太 ...
- echart关系图平分节点删除时自动平衡问题
项目场景: 项目场景:Echarts关系图中(不是力图)一个节点x,y是固定的,为了同一列能居中显示,规定:当前列有奇数个元素新节点往下放,有偶数个节点时新节点往上放. 问题描述: 删除中间节点会有空 ...
- python argparse (更新中)
action='store_true' 例如 parser.add_argument("--generate_text_embedding", action='store_true ...
- IP的地址的划分
IP地址的划分是计算机网络中很重要的一个知识点,曾经考过三级,但是长时间不用就会忘掉,现在重新将IP的地址划分整理一遍. 首先IP地址的编址方法经历了三个阶段:分类的IP地址.子网的划分.构成超网 我 ...
- JBoss 5.x和6.x 反序列化漏洞(CVE-2017-12149)
0x01 漏洞简介 该漏洞为 Java反序列化错误类型,存在于 Jboss 的 HttpInvoker 组件中的 ReadOnlyAccessFilter过滤器中.该过滤器在没有进行任何安全检查的情况 ...