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 。
众所周知Java的测试框架比较有名是TestNG、Junit,今本次架构师课程我给大家讲解Junit5框架的基本使用和改造,为什么会现在Junit5也是有很多原因的,对于这2款测试框架我在实践过程中我都用过,至于选择Junit5:
**一个标准的测试用例**
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() {
}
}
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@Disabled
class DisabledTestsDemo {
@Disabled
@Test
void testWillBeSkipped() {
}
@Test
void testWillBeExecuted() {
}
}
## 可以在测试类或测试方法或两者上应用@Tag注释
@Tag("development")
public class ClassATest
{
@Test
@Tag("userManagement")
void testCaseA(TestInfo testInfo) {
}
}
## 在单个测试用例上应用多个标签,以便您可以将其包含在多个测试计划中
public class ClassATest
{
@Test
@Tag("development")
@Tag("v_486")
void testCaseA() {
}
}
//@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 {
}
## 使用定义的元注解Tag
public class ClassATest
{
@Test
@Tag("development")
@Fast
void testCaseA() {
}
}
assertEquals(判断两个对象或两个原始类型是否相等)
assertNotEquals(判断两个对象或两个原始类型是否不相等)
assertSame(判断两个对象引用是否指向同一个对象)
assertNotSame(判断两个对象引用是否指向不同的对象)
assertTrue(判断给定的布尔值是否为 true)
assertFalse(判断给定的布尔值是否为 false)
assertNull (判断给定的对象引用是否为 null)
assertNotNull(判断给定的对象引用是否不为 null)
@Test
void groupedAssertions() {
assertAll("assertAll",
() -> assertEquals("Jane", 23),
() -> assertEquals("Doe", 23)
);
}
@Test
void 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"))
);
}
);
}
// 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 断言**/
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
//测试list中是否还有指值
assertThat(list, hasItem("b"));
/**Map 断言**/
Map<String,String> map = new HashMap<>();
map.put("a", "test1");
map.put("b", "test2");
//测试map中是否还有指定键值对
assertThat(map, hasEntry("a", "test1"));
//测试map中是否还有指定键
assertThat(map, hasKey("b"));
//测试map中是否还有指定值
assertThat(map, hasValue("test2"));
上面我们介绍了Junit5的基本使用后,下面我们来看看注解和反射,为什么我们要学习注解和反射,这块在设计测试框架以及多框架功能扩展的时候我们就会用到注解和反射,那下面我们来看下注解和反射:
注解的基本概念
注解(Annotation)提供了一种关联信息以及元数据的途径和方法。是一个接口,程序可以通过反射来获取指定程序元素中的 Annotation 对象,然后通过解析 Annotation 对象获取注解中的元数据。可以应用于包、类型、构造方法、方法、成员变量、参数、局部变量等等的声明中。在注解中以"name = value"的形式存储。
Annotation 不能影响程序代码的执行,尽管一些注解通过反射技术可以在运行时被访问,但是java的语言解释器在工作时是忽略他们的。
含义: 指定注解修饰的对象的范围,通俗的讲就是注解使用的时候要放在哪里(方法上,类上等等)取值(ElementType):
1、CONSTRUCTOR: 描述构造器
2、FIELD: 描述域属性
3、LOCAL_VARIABLE: 描述局部变量
4、METHOD: 描述方法
5、PACKAGE: 描述包
6、PARAMETER: 描述参数
7、TYPE: 描述类、接口或注解、枚举类型enum
含义: 定义注解保留的时长,限制注解的生命周期。取值(RetentionPolicy)
SOURCE: 源文件保留,被编译器所丢弃
CLASS: 在字节码文件(*.class)中保留,被JVM所丢弃
RUNTIME: 在运行时保留
含义: 被标注的类型是被继承的,使用 @Inherited 修饰的类型作用于一个 class 上时,那么注解也将应用在该 class 的子类。
含义: 可以使用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;
}
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(){
}
}
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");
}
}
**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() {
}
}
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("商品搜索");
}
@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 {
}
**${version}表示测试用例中@Tag的值**
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M3</version>
<configuration>
<groups>${version}</groups>
<testFailureIgnore>true</testFailureIgnore>
</configuration>
<dependencies>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-surefire-provider</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.6.2</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
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);
}
}