JUnit5学习之五:标签(Tag)和自定义注解

    科技2022-07-12  132

    欢迎访问我的GitHub

    这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos

    关于《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…: 如下图红框,在弹出的窗口上新增一个JUnit配置: 接下来的操作如下图所示,Test kind选择Tags,就会按照标签过滤测试方法,Tag expression里面填写过滤规则,后面会详细讲解这个规则,这里先填个已存在的标签important: 创建好JUnit配置后,执行下图红框中的操作即可执行单元测试: 执行结果如下,所有打了important标签的测试方法被执行:

    用maven命令时做标签过滤

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

    执行完毕后结果如下:

    翻看日志,可见只有打了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: 再次执行这个配置,结果如下图红框所示,只有这三个方法被执行:first1Test、first3Test、second3Test,可见标签表达式生效了: 在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来过滤: 标签名不能有这六个符号, ( ) & | ! 至此,JUnit5的标签过滤和自定义注解功能都学习完成了,有了这些能力,咱们可以更加灵活和随心所欲的应付不同的场景和需求;

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

    微信搜索「程序员欣宸」,我是欣宸,期待与您一同畅游Java世界…

    Processed: 0.012, SQL: 8