扫码阅读
手机扫码阅读

Junit5框架详解

89 2024-04-08

1、Junit5初识

1.1、what is junit5

JUnit5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

**JUnit Platform:**是在JVM上启动测试框架的基础。它还定义了用于开发平台上运行的测试框架的测试引擎(TestEngine)API。此外,该平台还提供了一个控制台启动器,可以从命令行启动平台,并为Gradle 和 Maven 构建插件,以及一个基于JUnit 4的运行器(JUnit 4 based Runner),用于在平台上运行任何 TestEngine

**JUnit Jupiter:**是在JUnit 5中编写测试和扩展的新编程模型和扩展模型的组合。另外,Jupiter子项目还提供了一个TestEngine,用于在平台上运行基于Jupiter的测试。

**JUnit Vintage:**提供了一个在平台上运行JUnit 3和JUnit 4的 TestEngine

1.2、why Junit5

众所周知Java的测试框架比较有名是TestNG、Junit,今本次架构师课程我给大家讲解Junit5框架的基本使用和改造,为什么会现在Junit5也是有很多原因的,对于这2款测试框架我在实践过程中我都用过,至于选择Junit5:

  • 相比Junit4、TestNG功能更强大

  • 完全兼容Spring、SpringBoot,这一点很重要

  • 标准化、可扩展性强

1.3、生命周期

** 一个标准的测试用例**

import static org.junit.jupiter.api.Assertions.fail; import org.junit.jupiter.api.AfterAll;import org.junit.jupiter.api.AfterEach;import org.junit.jupiter.api.BeforeAll;import org.junit.jupiter.api.BeforeEach;import org.junit.jupiter.api.Disabled;import org.junit.jupiter.api.Test; class StandardTests {  @BeforeAll static void beforeAll() { }  @BeforeEach void beforeEach() { }  @AfterEach void afterEach() { }  @AfterAll static void afterAll() { }  @Test void demoTest1() { }  @Test void demoTest2() { }}

1.4、常用注解

  • @Disabled

    • 可以通过 @Disabled 注释、是否测试执行

    • @Disabled可以标记在测试类、测试方法上

import org.junit.jupiter.api.Disabled;import org.junit.jupiter.api.Test; @Disabledclass DisabledTestsDemo {  @Disabled @Test void testWillBeSkipped() { }  @Test void testWillBeExecuted() { }} 
  • @Tag

    • JUnit5@Tag可用于从测试计划中过滤测试用例

    • 它可以帮助针对不同的环境,不同的用例或任何特定要求创建多个不同的测试计划

    • 通过仅在测试计划中包括@Tag标记的测试或通过从测试计划中排除其他测试来执行测试集

## 可以在测试类或测试方法或两者上应用@Tag注释@Tag("development")public class ClassATest{ @Test @Tag("userManagement") void testCaseA(TestInfo testInfo) { }}
## 在单个测试用例上应用多个标签,以便您可以将其包含在多个测试计划中public class ClassATest{ @Test @Tag("development") @Tag("v_486") void testCaseA() { }}
    • Create test plans with @IncludeTags and  @ExcludeTags

//@IncludeTags example@RunWith(JUnitPlatform.class)@SelectPackages("com.testcases.testops.order")@IncludeTags("v_486")public class MultipleTagsExample {} //@ExcludeTags example@RunWith(JUnitPlatform.class)@SelectPackages("com.testcases.testops.order")@ExcludeTags("v_486")public class MultipleTagsExample {}
    • 元注解和组合注解

import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target; import org.junit.jupiter.api.Tag; @Target({ ElementType.TYPE, ElementType.METHOD })@Retention(RetentionPolicy.RUNTIME)@Tag("v_486")public @interface Fast {}  ## 使用定义的元注解Tagpublic class ClassATest{ @Test @Tag("development") @Fast void testCaseA() { }}

1.5、断言

  • junit常用断言

assertEquals(判断两个对象或两个原始类型是否相等)assertNotEquals(判断两个对象或两个原始类型是否不相等)assertSame(判断两个对象引用是否指向同一个对象)assertNotSame(判断两个对象引用是否指向不同的对象)assertTrue(判断给定的布尔值是否为 trueassertFalse(判断给定的布尔值是否为 falseassertNull (判断给定的对象引用是否为 nullassertNotNull(判断给定的对象引用是否不为 null
  • assertAll

@Testvoid groupedAssertions() { assertAll("assertAll", () -> assertEquals("Jane", 23), () -> assertEquals("Doe", 23) );} @Testvoid dependentAssertions() { assertAll("assertAll", () -> { String firstName = null; assertNotNull(firstName); assertAll("assert1", () -> assertTrue(firstName.startsWith("J")), () -> assertTrue(firstName.endsWith("e")) ); }, () -> { String lastName = "De"; assertNotNull(lastName); assertAll("assertAll2", () -> assertTrue(lastName.startsWith("D")), () -> assertTrue(lastName.endsWith("e")) ); } );}
  • Junit5第三方断言

// AssertJ、Hamcrest、Truth// https://assertj.github.io/doc/ /**字符串 断言**/String url = "http://www.baidu.com";//测试变量是否包含指定字符assertThat(url, containsString("baidu"));//测试变量是否已指定字符串开头assertThat(url, startsWith("http://"));//测试变量是否以指定字符串结尾assertThat(url, endsWith(".com"));//测试变量是否等于指定字符串assertThat(url, equalTo("http://www.baidu.com"));//测试变量再忽略大小写的情况下是否等于指定字符串assertThat(url, equalToIgnoringCase("http://www.baidu.com"));//测试变量再忽略头尾任意空格的情况下是否等于指定字符串assertThat(url, equalToIgnoringWhiteSpace("http://www.baidu.com")); /**List 断言**/Listlist = new ArrayList<>();list.add("a");list.add("b");//测试list中是否还有指值assertThat(list, hasItem("b")); /**Map  断言**/Mapmap = new HashMap<>();,string>map.put("a", "test1");map.put("b", "test2"); //测试map中是否还有指定键值对assertThat(map, hasEntry("a", "test1"));//测试map中是否还有指定键assertThat(map, hasKey("b"));//测试map中是否还有指定值assertThat(map, hasValue("test2"));

2、Java注解和反射

上面我们介绍了Junit5的基本使用后,下面我们来看看注解和反射,为什么我们要学习注解和反射,这块在设计测试框架以及多框架功能扩展的时候我们就会用到注解和反射,那下面我们来看下注解和反射:

2.1、注解

注解的基本概念

注解(Annotation)提供了一种关联信息以及元数据的途径和方法。是一个接口,程序可以通过反射来获取指定程序元素中的 Annotation 对象,然后通过解析 Annotation 对象获取注解中的元数据。可以应用于包、类型、构造方法、方法、成员变量、参数、局部变量等等的声明中。在注解中以"name = value"的形式存储。

Annotation 不能影响程序代码的执行,尽管一些注解通过反射技术可以在运行时被访问,但是java的语言解释器在工作时是忽略他们的。

2.2、注解定义

  • @Target

含义: 指定注解修饰的对象的范围,通俗的讲就是注解使用的时候要放在哪里(方法上,类上等等)取值(ElementType):

1、CONSTRUCTOR: 描述构造器

2、FIELD: 描述域属性

3、LOCAL_VARIABLE: 描述局部变量

4、METHOD: 描述方法

5、PACKAGE: 描述包

6、PARAMETER: 描述参数

7、TYPE: 描述类、接口或注解、枚举类型enum

  • @Retention

含义: 定义注解保留的时长,限制注解的生命周期。取值(RetentionPolicy)

SOURCE: 源文件保留,被编译器所丢弃

CLASS: 在字节码文件(*.class)中保留,被JVM所丢弃

RUNTIME: 在运行时保留

  • @Inherited

含义: 被标注的类型是被继承的,使用 @Inherited 修饰的类型作用于一个 class 上时,那么注解也将应用在该 class 的子类。

  • @Documented

含义: 可以使用javadoc工具进行文档化操作

import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target; /** * @Auther: Evan.hu * @Description: 定义注解 * @date: 2022/3/6 10:30 */@Target({ ElementType.TYPE, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)public @interface Table { String value() default "";}
import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target; /** * @Auther: Evan.hu * @Description: 定义注解 * @date: 2022/3/6 10:30 */@Target({ ElementType.TYPE, ElementType.METHOD,ElementType.FIELD})@Retention(RetentionPolicy.RUNTIME)public @interface Column {  String column() default ""; String type() default ""; int length() default 0;}

2.3、使用注解

import com.platform.annotation.Column;import com.platform.annotation.Table; @Table(value = "mysql")public class TableTest { @Column(column = "名字", type = "java.lang.String", length = 12) private String name;  @Column(column = "年龄", type = "int", length = 2) private int age;  @Column(column = "地址", type = "String") private String address;  @Table(value = "mysql") public void test1(){  }}

2.4、通过反射解析注解

import com.platform.annotation.Column;import com.platform.annotation.Table;import java.lang.annotation.Annotation;import java.lang.reflect.Field;import java.lang.reflect.Method; public class AnnotationUtil { public static void parse(Class target){ Table table = target.getAnnotation(Table.class); System.out.println("数据库表名: " + table.value());  Field[] fields = target.getDeclaredFields();  for (Field field : fields){ Column column = field.getAnnotation(Column.class); System.out.println(column.column()); System.out.println(column.type()); System.out.println(column.length()); } }  public static void parse(String packagse,String method) throws ClassNotFoundException { Class clazz = Class.forName(packagse);  // 获取类上面所有注解 Annotation[] annotations = clazz.getAnnotations(); for (Annotation a : annotations){ if (a instanceof Table){ Table table = (Table) clazz.getAnnotation(Table.class); System.out.println("数据库表名: " + table.value()); } }  // 获取类下面所有方法 Method[] methods = clazz.getDeclaredMethods(); for (Method m : methods){ if (m.getName().equals(method)){ System.out.println("方法里面注解解析"); Table t = m.getAnnotation(Table.class); System.out.println(t.value()); }else { System.out.println("不需要处理的注解方法"); } }  Field[] fields = clazz.getDeclaredFields(); for (Field field : fields){ Annotation[] annotations1 = field.getAnnotations(); for (Annotation a1 : annotations1){ if (a1 instanceof Column){ Column column = field.getAnnotation(Column.class); System.out.println(column.column()); System.out.println(column.type()); System.out.println(column.length()); } } } }  public static void main(String[] args) throws ClassNotFoundException { String packages = "com.testcases.junit5.TableTest";//        AnnotationUtil.parse(TableTest.class); AnnotationUtil.parse(packages,"test1"); }}

4、junit5高级特性

4.1、@TestMethodOrder

**junit5指定@Test的执行顺序,Order值越小越优先执行**

**根据此注解可以自定义场景测试用例**

 @TestMethodOrder(MethodOrderer.OrderAnnotation.class)public class demo1Test {  @Test @Order(0) @DisplayName("用户登录") public void test1() { }  @Test @Order(2) @DisplayName("搜索下单商品") public void test2(){ }  @Test @Order(3) @DisplayName("下单支付") public void test3() { }}

4.2、@EnabledIf

EnabledIf自定义执行条件函数:com.testcases.condition.Condition#isHasTestDate通过isHasTestDate方法的返回boolean来判断改测试用例是否执行

package com.testcases.condition; public class Condition { public static boolean customCondition(){ return true; }  public static boolean isHasTestDate(){ return true; }}  @Test@Order(2)@EnabledIf("com.testcases.condition.Condition#isHasTestDate")@DisplayName("搜索下单商品2")public void test2(){ System.out.println("商品搜索");}

4.3、测试计划执行

  • junit5套件执行

@RunWith(JUnitPlatform.class)@SelectClasses({ExportTest.class, Export123Test.class})public class ClassSuite {}
@RunWith(JUnitPlatform.class)@SelectPackages({"com.testcases.template.demo2"})public class PackagesSuite { }
@RunWith(JUnitPlatform.class)@SelectPackages({"com.testcases.template.demo2"})@IncludeTags("v_486")public class PackagesSuite { }
@RunWith(JUnitPlatform.class)@SelectPackages({"com.testcases.template.demo2"})@ExcludeTags("v_486")public class PackagesSuite { }
  • maven执行

**${version}表示测试用例中@Tag的值**

<build> <plugins> <plugin> <artifactId>maven-surefire-pluginartifactId> <version>3.0.0-M3version> <configuration> <groups>${version}groups> <testFailureIgnore>truetestFailureIgnore> configuration> <dependencies> <dependency> <groupId>org.junit.platformgroupId> <artifactId>junit-platform-surefire-providerartifactId> <version>1.3.2version> dependency> <dependency> <groupId>org.junit.jupitergroupId> <artifactId>junit-jupiter-engineartifactId> <version>5.6.2version> dependency> dependencies> plugin> plugins> build>

  • Java代码执行

public class LauncherMain {  public static void clazz(Class clazz){ try{ LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request() .selectors( selectClass(clazz) ) .build();  log.info("---------start----------"); Launcher launcher = LauncherFactory.create();  SummaryGeneratingListener listener = new SummaryGeneratingListener(); launcher.registerTestExecutionListeners(listener); launcher.execute(request); TestExecutionSummary summary = listener.getSummary(); log.info("---------end----------"); }catch (Throwable throwable){ } }} public class TestLauncherMain { public static void main(String[] args) { LauncherMain.clazz(demo1Test.class); }}

5、总结

  • 框架思维不仅仅是设计一个框架

    • 技术架构

    • 产品思维

原文链接: https://mp.weixin.qq.com/s?__biz=MzU5ODE2OTc1OQ==&mid=2247495361&idx=1&sn=1472c4829ff7bdc697f1231b5714abd8