一:背景
源码地址:GitHub - 18713341733/AutoApi
Java 接口自动化框架,一般就是junit与testng。这里我们讲一下junit接口自动化框架的二次开发。
1.1 我们实现了哪些功能
1、自定义了一些注解,用来管理case。包括case的描述、作者、对case进行分组等
2、对case运行结果做了一个报警处理,将运行结果通过钉钉/企业微信发送测试报告
3、简单封装了一下http的请求。
4、数据库的连接
本篇文章,只是简单大体的讲解一下整个项目的结构。具体每个功能,实现的细节,可以看这几章:
三、junit接口自动化框架-自定义注解-注解检查_傲娇的喵酱的博客-CSDN博客
四、junit接口自动化框架-根据一个注解筛选case_做测试的喵酱的博客-CSDN博客
五、junit接口自动化框架-根据多个注解组合筛选case_做测试的喵酱的博客-CSDN博客_@caseselector
六、junit接口自动化框架-单个&批量case触发报警的逻辑_做测试的喵酱的博客-CSDN博客_如何自动触发junit跑批入口
七、钉钉机器人报警设置_做测试的喵酱的博客-CSDN博客_钉钉报警机器人
八、junit接口自动化框架-钉钉发送报告_做测试的喵酱的博客-CSDN博客_@caseselector
九、junit接口自动化框架-构造http请求_做测试的喵酱的博客-CSDN博客_juint 设置header
十、使用责任链模式,重构http请求_做测试的喵酱的博客-CSDN博客_httprequet 请求责任链
十一、junit5 自定义参数化注解_做测试的喵酱的博客-CSDN博客_junit5 自定义参数化注解
十二、junit5 框架之mysql client 数据库链接_做测试的喵酱的博客-CSDN博客_junit 数据库连接
二、junit5基本注解
在进行二次开发之前,先了解一下junit5的基本注解
2.1 @BeforeAll @BeforeEach @AfterAll @AfterEach
注意:@BeforeAll 与 @AfterAll 标签下 的两个方法,是静态方法,不然报错。
package com.example.autoapi.test;
import org.junit.jupiter.api.*;
public class Junit5Demo {
@BeforeAll
public static void beforeAll(){
System.out.println("Junit5Demo.beforeAll");
}
@BeforeEach
public void beforeEach(){
System.out.println("Junit5Demo.beforeEach");
}
@AfterAll
public static void afterAll(){
System.out.println("Junit5Demo.AfterAll");
}
@AfterEach
public void afterEach(){
System.out.println("Junit5Demo.afterEach");
}
@Test
public void test1(){
System.out.println("Junit5Demo.test1");
}
@Test
public void test2(){
System.out.println("Junit5Demo.test1");
}
}
输出结果:
Junit5Demo.beforeAll
Junit5Demo.beforeEach
Junit5Demo.test1
Junit5Demo.afterEach
Junit5Demo.beforeEach
Junit5Demo.test1
Junit5Demo.afterEach
Junit5Demo.AfterAll
2.2 @Tag @Timeout @Test
每条测试case,需要打@Test
@Tag 给case打标签,比如级别P0
@Timeout 超时,单位是秒,测试case执行超过这个时间,报错
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
public class Junit5Demo2 {
@Tag("P0")
@Timeout(2)
@Test
public void testNormal(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Junit5Demo2.testNormal");
}
}
三:spring boot项目
新建一个spring boot项目,项目名称为AutoApi。(注意看图)
Spring Boot 我们使用2.4.4版本。这里能选到就选,选不到就随便选一个,后续我们在pom文件里改spring boot版本
Web--> 勾选 Spring Web
新建完成后,去修改 pom文件,改成2.4.4
删除这几个文件,用不到。只保留src .gitignore pom.xml三个文件
3.1 pom.xml文件
我提前把整个项目需要用到的依赖,先写到文件里。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>AutoApi</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>AutoApi</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.13</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.1.1-jre</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
我们基于junit进行的二次开发,所以要倒入junit包,以下四个:
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.6.2</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.6.2</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.6.2</version>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<version>1.6.2</version>
</dependency>
3.2 、整个项目结构
四、自定义注解
4.1 AutoTest
关联了注解的扩展方法。
package com.example.autoapi.annotation;
import com.example.autoapi.extension.CaseAnnotationCheckExtension;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.ANNOTATION_TYPE,ElementType.METHOD}) // Target表示将来这个注解应用在哪些方面。注解可以应用在类上、方法上(此处只应用在方法上)
@Retention(RetentionPolicy.RUNTIME) //运行时,使用这个这个注解
@Test
@ExtendWith(CaseAnnotationCheckExtension.class) // 与junit结合,由junit提供。自定义一个扩展类,然后放到这里。走我们的自定义类
public @interface AutoTest {
}
4.2 case进行管理的注解
CaseDesc:对case进行描述
package com.example.autoapi.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.ANNOTATION_TYPE,ElementType.METHOD}) // Target表示将来这个注解应用在哪些方面。注解可以应用在类上、方法上(此处只应用在方法上)
@Retention(RetentionPolicy.RUNTIME) //运行时,使用这个这个注解
public @interface CaseDesc {
String desc();
String owner();
}
CaseGroup:对case进行分组
package com.example.autoapi.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.ANNOTATION_TYPE,ElementType.METHOD}) // Target表示将来这个注解应用在哪些方面。注解可以应用在类上、方法上(此处只应用在方法上)
@Retention(RetentionPolicy.RUNTIME) //运行时,使用这个这个注解
public @interface CaseGroup {
String team();
String group();
}
CaseSelector :筛选case,对case进行管理
四、junit接口自动化框架-根据一个注解筛选case_做测试的喵酱的博客-CSDN博客
package com.example.autoapi.annotation;
import com.example.autoapi.extension.CaseAnnotationCheckExtension;
import com.example.autoapi.extension.CaseSelectorExtension;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.ANNOTATION_TYPE,ElementType.METHOD}) // Target表示将来这个注解应用在哪些方面。注解可以应用在类上、方法上(此处只应用在方法上)
@Retention(RetentionPolicy.RUNTIME) //运行时,使用这个这个注解
@Test
@ExtendWith(CaseSelectorExtension.class) // 与junit结合,由junit提供。自定义一个扩展类,然后放到这里。走我们的自定义类
public @interface CaseSelector {
String scanPackage();
String key() default "";
String val() default "";
String team() default "";
String group() default "";
}
CaseTag 自定义tag
package com.example.autoapi.annotation;
import java.lang.annotation.*;
@Target({ElementType.ANNOTATION_TYPE,ElementType.METHOD}) // Target表示将来这个注解应用在哪些方面。注解可以应用在类上、方法上(此处只应用在方法上)
@Retention(RetentionPolicy.RUNTIME) //运行时,使用这个这个注解
@Repeatable(CaseTags.class) // 支持多个CaseTag
public @interface CaseTag {
String key();
String val();
}
CaseTags
package com.example.autoapi.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.ANNOTATION_TYPE,ElementType.METHOD}) // Target表示将来这个注解应用在哪些方面。注解可以应用在类上、方法上(此处只应用在方法上)
@Retention(RetentionPolicy.RUNTIME) //运行时,使用这个这个注解
public @interface CaseTags {
CaseTag[] value();
}
CaseTitle
package com.example.autoapi.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.ANNOTATION_TYPE,ElementType.METHOD}) // Target表示将来这个注解应用在哪些方面。注解可以应用在类上、方法上(此处只应用在方法上)
@Retention(RetentionPolicy.RUNTIME) //运行时,使用这个这个注解
public @interface CaseTitle {
String value();
}
CheckPoint:测试case的检查点
package com.example.autoapi.annotation;
import java.lang.annotation.*;
@Target({ElementType.ANNOTATION_TYPE,ElementType.METHOD}) // Target表示将来这个注解应用在哪些方面。注解可以应用在类上、方法上(此处只应用在方法上)
@Retention(RetentionPolicy.RUNTIME) //运行时,使用这个这个注解
@Repeatable(CheckPoints.class) // 支持写多个注解@CheckPoint()@CheckPoint()
public @interface CheckPoint {
String value();
}
4.3 DataYml
package com.example.autoapi.annotation;
import com.example.autoapi.extension.CaseAnnotationCheckExtension;
import com.example.autoapi.extension.DataYmlExtension;
import org.junit.jupiter.api.TestTemplate;
import org.junit.jupiter.api.extension.ExtendWith;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.ANNOTATION_TYPE,ElementType.METHOD}) // Target表示将来这个注解应用在哪些方面。注解可以应用在类上、方法上(此处只应用在方法上)
@Retention(RetentionPolicy.RUNTIME) //运行时,使用这个这个注解
@ExtendWith(DataYmlExtension.class) // 与junit结合,由junit提供。自定义一个扩展类,然后放到这里。走我们的自定义类
@TestTemplate //关键注解
@ExtendWith(CaseAnnotationCheckExtension.class) // 与junit结合,由junit提供。自定义一个扩展类,然后放到这里。走我们的自定义类
public @interface DataYml {
String path();
}
4.4 DingTalkAlarm 钉钉报警的注解
package com.example.autoapi.annotation;
import com.example.autoapi.alarm.callback.AlarmCallBack;
import com.example.autoapi.extension.AlarmExtension;
import org.junit.jupiter.api.extension.ExtendWith;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.ANNOTATION_TYPE,ElementType.METHOD}) // Target表示将来这个注解应用在哪些方面。注解可以应用在类上、方法上(此处只应用在方法上)
@Retention(RetentionPolicy.RUNTIME) //运行时,使用这个这个注解
@ExtendWith(AlarmExtension.class) // 与junit结合,由junit提供。自定义一个扩展类,然后放到这里。走我们的自定义类
public @interface DingTalkAlarm {
String token();
Class<? extends AlarmCallBack> alarmCallBack();
}
4.5 ReportConfig :报告模版的注解
package com.example.autoapi.annotation;
import com.example.autoapi.report.ReportType;
import com.example.autoapi.report.callback.ReportCallBack;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.ANNOTATION_TYPE,ElementType.METHOD}) // Target表示将来这个注解应用在哪些方面。注解可以应用在类上、方法上(此处只应用在方法上)
@Retention(RetentionPolicy.RUNTIME) //运行时,使用这个这个注解
public @interface ReportConfig {
String token();
ReportType reportType() default ReportType.DING_TALK;
Class<? extends ReportCallBack> callback();
String template() default "default_report_template";
}
五、对自定义注解的检查 ,check层
我们自定义了很多注解,在使用过程中,注解应该按照规定格式填写,为了防止用户填写注解时,格式出错,我们这里用观察者模式对注解进行检查。
AnnotationCheckObserver
package com.example.autoapi.check;
import java.lang.reflect.Method;
// 观察者模式
// 每个注解的检查都实现这个接口,便于统一管理/校验注解
public interface AnnotationCheckObserver {
void check(Method method);
}
CaseDescCheck
package com.example.autoapi.check;
import com.example.autoapi.annotation.CaseDesc;
import com.example.autoapi.annotation.CaseTitle;
import com.example.autoapi.exception.IllegaFormatException;
import com.example.autoapi.util.RequireUtil;
import java.lang.reflect.Method;
public class CaseDescCheck implements AnnotationCheckObserver{
@Override
public void check(Method method){
boolean caseDescSet = method.isAnnotationPresent(CaseDesc.class);
// 判断注解CaseTitle是否存在
if(!caseDescSet){
throw new IllegaFormatException("CaseDesc is must");
}
CaseDesc caseDesc = method.getAnnotation(CaseDesc.class);
RequireUtil.requireNotNullOrEmpty(caseDesc.desc(),"CaseDesc desc is must.eg: @CaseDesc(desc=\"描述\",owner=\"管理者\")");
RequireUtil.requireNotNullOrEmpty(caseDesc.owner(),"CaseDesc owner is must.eg: @CaseDesc(desc=\"描述\",owner=\"管理者\")");
}
}
CaseGroupCheck
package com.example.autoapi.check;
import com.example.autoapi.annotation.CaseDesc;
import com.example.autoapi.annotation.CaseGroup;
import com.example.autoapi.exception.IllegaFormatException;
import com.example.autoapi.util.RequireUtil;
import java.lang.reflect.Method;
public class CaseGroupCheck implements AnnotationCheckObserver{
@Override
public void check(Method method){
boolean caseGroupSet = method.isAnnotationPresent(CaseGroup.class);
// 判断注解CaseGroup是否存在
// CaseGroup 非必填
if(!caseGroupSet){
return;
}
CaseGroup caseGroup = method.getAnnotation(CaseGroup.class);
RequireUtil.requireNotNullOrEmpty(caseGroup.team(),"CaseGroup team is must.eg: @CaseGroup(team=\"xx\",group=\"xx\")");
RequireUtil.requireNotNullOrEmpty(caseGroup.group(),"CaseGroup group is must.eg: @CaseGroup(team=\"xx\",group=\"xx\")");
}
}
CaseTagCheck
package com.example.autoapi.check;
import com.example.autoapi.annotation.CaseDesc;
import com.example.autoapi.annotation.CaseTag;
import com.example.autoapi.exception.IllegaFormatException;
import com.example.autoapi.util.RequireUtil;
import java.lang.reflect.Method;
public class CaseTagCheck implements AnnotationCheckObserver{
@Override
public void check(Method method){
CaseTag[] caseTags = method.getAnnotationsByType(CaseTag.class);
// 判断是否有CaseTag注解
// 因为可以写多个,这里获取的是一个数组
// 数组长度为0时,报异常
if(caseTags.length==0){
throw new IllegaFormatException("CaseTag is must");
}
for(CaseTag caseTag:caseTags){
RequireUtil.requireNotNullOrEmpty(caseTag.key(),"CaseTag key is must.eg: @CaseTag(key=\"key\",val=\"val\")");
RequireUtil.requireNotNullOrEmpty(caseTag.val(),"CaseTag val is must.eg: @CaseTag(key=\"key\",val=\"val\")");
}
}
}
CaseTitleCheck
package com.example.autoapi.check;
import com.example.autoapi.annotation.CaseTitle;
import com.example.autoapi.exception.IllegaFormatException;
import com.example.autoapi.util.RequireUtil;
import java.lang.reflect.Method;
public class CaseTitleCheck implements AnnotationCheckObserver{
@Override
public void check(Method method){
boolean titleSet = method.isAnnotationPresent(CaseTitle.class);
// 判断注解CaseTitle是否存在
if(!titleSet){
throw new IllegaFormatException("title is must");
}
CaseTitle caseTitle = method.getAnnotation(CaseTitle.class);
RequireUtil.requireNotNullOrEmpty(caseTitle.value(),"CaseTitle value is must.eg: @CaseTitle(\"标题\")");
}
}
CheckPointCheck
package com.example.autoapi.check;
import com.example.autoapi.annotation.CaseTag;
import com.example.autoapi.annotation.CheckPoint;
import com.example.autoapi.exception.IllegaFormatException;
import com.example.autoapi.util.RequireUtil;
import java.lang.reflect.Method;
public class CheckPointCheck implements AnnotationCheckObserver{
@Override
public void check(Method method){
CheckPoint[] checkPoints = method.getAnnotationsByType(CheckPoint.class);
// 判断是否有CaseTag注解
// 因为可以写多个,这里获取的是一个数组
// 数组长度为0时,报异常
if(checkPoints.length==0){
throw new IllegaFormatException("CheckPoint is must");
}
for(CheckPoint checkPoint:checkPoints){
RequireUtil.requireNotNullOrEmpty(checkPoint.value(),"CheckPoint is must.eg: @CheckPoint(xxx)");
}
}
}
ObserverManager
将各个检查方法,穿成一个集合。
package com.example.autoapi.check;
import com.google.common.collect.Lists;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
public class ObserverManager {
private final List<AnnotationCheckObserver> observers;
// 构造器,私有,不能被new
private ObserverManager(){
observers = Lists.newArrayList(
new CaseTitleCheck(),
new CaseDescCheck(),
new CaseTagCheck(),
new CheckPointCheck(),
new CaseGroupCheck()
);
}
// ClassHolder属于静态内部类,在加载类Demo03的时候,只会加载内部类ClassHolder,
// 但是不会把内部类的属性加载出来
private static class ClassHolder{
// 这里执行类加载,是jvm来执行类加载,它一定是单例的,不存在线程安全问题
// 这里不是调用,是类加载,是成员变量
private static final ObserverManager holder =new ObserverManager();
}
public static ObserverManager of(){//第一次调用getInstance()的时候赋值
return ClassHolder.holder;
}
public void check(Method method){
// 把case/方法 method ,依次接受各种检查
for(AnnotationCheckObserver observer:observers){
observer.check(method);
}
}
}
六、自定义注解的扩展功能extension
上面只是规定了自定义注解的格式,及对用户输入注解的格式进行检查。
extension 层,写了注解具体的运行逻辑。
6.1 运行时,筛选case功能的扩展
AbstractDiscoveryFilter,筛选case的扩展
package com.example.autoapi.extension.filter;
import com.example.autoapi.annotation.CaseSelector;
import org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor;
import org.junit.platform.engine.FilterResult;
import org.junit.platform.engine.TestDescriptor;
import org.junit.platform.launcher.PostDiscoveryFilter;
public abstract class AbstractDiscoveryFilter implements PostDiscoveryFilter {
// preFilter 判断是否执行当前过滤器,为true执行,为false不执行
// onApply,具体的执行过滤,preFilter 为true时,执行onApply
protected CaseSelector caseSelector;
public AbstractDiscoveryFilter(CaseSelector caseSelector) {
this.caseSelector = caseSelector;
}
protected abstract boolean preFilter(CaseSelector caseSelector);
// 类似于责任链的设计模式。是否需要当前过滤器执行
// 为true,需要执行当前过滤器,则调用下方的onApply方法,去实行过滤
// 为false,不需要执行当前过滤器。交给下一个过滤器去执行
// 这个方法是抽象的,交给子类去实现
protected abstract FilterResult onApply(TestMethodTestDescriptor testDescriptor);
@Override
public FilterResult apply(TestDescriptor testDescriptor) {
if(!preFilter(caseSelector)){ // 如果不需要过滤
return FilterResult.includedIf(true); // 返回true,放过所有的case。
}
if(testDescriptor instanceof TestMethodTestDescriptor){
return onApply((TestMethodTestDescriptor)testDescriptor);
}
return FilterResult.includedIf(false);// false 全部拦住,正常情况下不会出现这种情况
}
}
CaseGroupFilter case分组过滤的扩展
package com.example.autoapi.extension.filter;
import com.example.autoapi.annotation.CaseGroup;
import com.example.autoapi.annotation.CaseSelector;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor;
import org.junit.platform.engine.FilterResult;
import org.junit.platform.engine.TestDescriptor;
import org.junit.platform.launcher.PostDiscoveryFilter;
import java.lang.reflect.Method;
public class CaseGroupFilter extends AbstractDiscoveryFilter {
public CaseGroupFilter(CaseSelector caseSelector) {
super(caseSelector);
}
@Override
protected boolean preFilter(CaseSelector caseSelector) {
return StringUtils.isNotBlank(caseSelector.group())&&StringUtils.isNotBlank(caseSelector.team());
// 判断注解CaseSelector 的group与team是否为空,若都不为空 ,启用我们这个筛选器
}
@Override
protected FilterResult onApply(TestMethodTestDescriptor testDescriptor) {
Method testMethod = testDescriptor.getTestMethod();
// 该注解是非必填的,所以这里判断一下注解是否存在。
// 其实我个人觉得这里没必要判断是否存在,因为用的这个模式,在场景里,需要调用到这个方法的时候,该注解肯定是存的
boolean caseGroupSet = testMethod.isAnnotationPresent(CaseGroup.class);
if(caseGroupSet){
CaseGroup caseGroup = testMethod.getAnnotation(CaseGroup.class);
if(caseGroup.group().equals(caseSelector.group())&&caseGroup.team().equals(caseSelector.team())){
return FilterResult.includedIf(true);
}
}
return FilterResult.includedIf(false);
}
}
CaseTagFilter 根据casetag过滤的扩展
package com.example.autoapi.extension.filter;
import com.example.autoapi.annotation.CaseSelector;
import com.example.autoapi.annotation.CaseTag;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor;
import org.junit.platform.engine.FilterResult;
import org.junit.platform.engine.TestDescriptor;
import org.junit.platform.launcher.PostDiscoveryFilter;
import java.lang.reflect.Method;
import java.util.Arrays;
public class CaseTagFilter extends AbstractDiscoveryFilter{
public CaseTagFilter(CaseSelector caseSelector) {
super(caseSelector);
}
@Override
protected boolean preFilter(CaseSelector caseSelector) {
return StringUtils.isNotBlank(caseSelector.key())&& StringUtils.isNotBlank(caseSelector.val());
}
@Override
protected FilterResult onApply(TestMethodTestDescriptor testDescriptor) {
Method testMethod = testDescriptor.getTestMethod();
CaseTag[] caseTags = testMethod.getAnnotationsByType(CaseTag.class);
long count = Arrays.stream(caseTags)
.filter(caseTag -> caseTag.key().equals(caseSelector.key()) && caseTag.val().equals(caseSelector.val()))
.count();
return count>0 ? FilterResult.includedIf(true):FilterResult.includedIf(false);
}
}
CaseAnnotationCheckExtension case注解检查的扩展
package com.example.autoapi.extension;
import com.example.autoapi.annotation.CaseTitle;
import com.example.autoapi.check.ObserverManager;
import org.junit.jupiter.api.extension.BeforeTestExecutionCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import java.lang.reflect.Method;
// 自定义的扩展类,必须实现框架提供的BeforeTestExecutionCallback接口
// BeforeTestExecutionCallback 在执行case之前,先执行我们的回调
public class CaseAnnotationCheckExtension implements BeforeTestExecutionCallback {
@Override
public void beforeTestExecution(ExtensionContext extensionContext) throws Exception {
Method requiredTestMethod = extensionContext.getRequiredTestMethod(); //拿到使用该注解的方法/测试case
ObserverManager.of().check(requiredTestMethod); // 利用观察者模式对case上的方法依次进行检查
}
}
CaseSelectorExtension case筛选的扩展
package com.example.autoapi.extension;
import com.example.autoapi.alarm.FailureListener;
import com.example.autoapi.alarm.callback.AlarmCallBack;
import com.example.autoapi.annotation.CaseSelector;
import com.example.autoapi.annotation.DingTalkAlarm;
import com.example.autoapi.annotation.ReportConfig;
import com.example.autoapi.extension.filter.CaseGroupFilter;
import com.example.autoapi.extension.filter.CaseTagFilter;
import com.example.autoapi.model.FailureResult;
import com.example.autoapi.model.SummaryResult;
import com.example.autoapi.util.ReflectUtils;
import com.example.autoapi.util.RequireUtil;
import org.junit.jupiter.api.extension.BeforeTestExecutionCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.platform.engine.TestSource;
import org.junit.platform.engine.discovery.DiscoverySelectors;
import org.junit.platform.engine.support.descriptor.MethodSource;
import org.junit.platform.launcher.Launcher;
import org.junit.platform.launcher.LauncherDiscoveryRequest;
import org.junit.platform.launcher.TestIdentifier;
import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder;
import org.junit.platform.launcher.core.LauncherFactory;
import org.junit.platform.launcher.listeners.SummaryGeneratingListener;
import org.junit.platform.launcher.listeners.TestExecutionSummary;
import java.lang.reflect.Method;
import java.util.List;
import java.util.stream.Collectors;
public class CaseSelectorExtension implements BeforeTestExecutionCallback{
@Override
public void beforeTestExecution(ExtensionContext extensionContext) throws Exception {
// 获取执行入口的方法
Method method = extensionContext.getRequiredTestMethod();
// 获取注解CaseSelector信息
CaseSelector caseSelector = method.getAnnotation(CaseSelector.class);
// 验证/校验 注解信息
verify(caseSelector);
//----开始进行是筛选----
// 先筛选包
// 再按照@CaseTag key value筛选
LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request()
// 根据包筛选case
.selectors(DiscoverySelectors.selectPackage(caseSelector.scanPackage()))
// 根据注解CaseTag筛选case
.filters(new CaseTagFilter(caseSelector))
.filters(new CaseGroupFilter(caseSelector))
.build();
Launcher launcher = LauncherFactory.create();
SummaryGeneratingListener summaryGeneratingListener = new SummaryGeneratingListener();
// 判断执行入口,是否有报警注解
boolean dingTalkSet = method.isAnnotationPresent(DingTalkAlarm.class);
if(dingTalkSet){
// 获取报警注解的属性
// 我们把具体的报警方式,写在了这个注解的属性里
DingTalkAlarm dingTalkAlarm = method.getAnnotation(DingTalkAlarm.class);
Class<? extends AlarmCallBack> alarmCallBack = dingTalkAlarm.alarmCallBack();
FailureListener failureListener = new FailureListener(dingTalkAlarm.token(),alarmCallBack);
launcher.execute(request,summaryGeneratingListener,failureListener );
} else {
launcher.execute(request,summaryGeneratingListener );
}
// ----以上固定结构,框架提供能力
// 根据CaseTag 筛选,这里是需要自己写的CaseTagFilter
boolean reportSet= method.isAnnotationPresent(ReportConfig.class);
if(reportSet){
TestExecutionSummary summary = summaryGeneratingListener.getSummary();
SummaryResult summaryResult = FromSummaryToSummaryResult(summary);
ReportConfig reportConfig = method.getAnnotation(ReportConfig.class);
ReflectUtils.newInstance(reportConfig.callback()).postReport(summaryResult, reportConfig.token(), reportConfig.template());
}
}
// 将框架提供的运行结果类TestExecutionSummary,转换成我们自定义的结果类
private SummaryResult FromSummaryToSummaryResult(TestExecutionSummary summary){
// 失败的原因
List<FailureResult> failureResults = summary.getFailures().stream()
.map(failure -> {
TestIdentifier testIdentifier = failure.getTestIdentifier();
TestSource testSource = testIdentifier.getSource().get();
MethodSource methodSource = (MethodSource) testSource;
Throwable throwable = failure.getException();
// 将具体的一条失败case相关信息封装在FailureResult中
FailureResult failureResult = FailureResult.builder()
.className(methodSource.getClassName()) //报错的 类名
.methodName(methodSource.getMethodName()) //报错的方法名/case名
.parameterTypes(methodSource.getMethodParameterTypes()) // 传参类型
.throwable(throwable) // 报错原因
.build();
return failureResult;
})
.collect(Collectors.toList());
SummaryResult summaryResult = SummaryResult.builder()
.totalCount(summary.getTestsFoundCount())
.failureCount(summary.getTotalFailureCount())
.successCount(summary.getTestsSucceededCount())
.startTime(summary.getTimeStarted())
.endTime(summary.getTimeFinished())
.failureResults(failureResults)
.build();
return summaryResult;
}
private void verify(CaseSelector caseSelector){
RequireUtil.requireNotNullOrEmpty(caseSelector.scanPackage(),"scanPackage is must");
}
}
6.2 报警注解的扩展
AlarmExtension
package com.example.autoapi.extension;
import com.example.autoapi.annotation.DingTalkAlarm;
import com.example.autoapi.model.FailureResult;
import com.example.autoapi.util.ReflectUtils;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.TestExecutionExceptionHandler;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.stream.Collectors;
//TestExecutionExceptionHandler 框架提供的能力,测试执行失败,触发的回调
public class AlarmExtension implements TestExecutionExceptionHandler {
@Override
public void handleTestExecutionException(ExtensionContext extensionContext, Throwable throwable) throws Throwable {
Method testMethod = extensionContext.getRequiredTestMethod();
Class<?> testClass = extensionContext.getRequiredTestClass();
Class<?>[] parameterTypes = testMethod.getParameterTypes();
System.out.println("AlarmExtension.handleTestExecutionException");
String params = Arrays.stream(parameterTypes).map(cls->cls.getName()).collect(Collectors.joining(","));
FailureResult failureResult = FailureResult.builder()
.className(testClass.getName())
.methodName(testMethod.getName())
.parameterTypes(params)
.throwable(throwable)
.build();
DingTalkAlarm dingTalkAlarm = testMethod.getAnnotation(DingTalkAlarm.class);
ReflectUtils.newInstance(dingTalkAlarm.alarmCallBack()).postAlarm(failureResult,dingTalkAlarm.token());
}
}
6.3 读取yaml文件的扩展
DataYmlExtension
package com.example.autoapi.extension;
import com.example.autoapi.annotation.DataYml;
import com.example.autoapi.model.DataEntity;
import com.example.autoapi.model.Entity;
import com.example.autoapi.util.YmlUtil;
import com.google.common.collect.Lists;
import com.google.gson.Gson;
import org.junit.jupiter.api.extension.*;
import org.junit.platform.commons.support.AnnotationSupport;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
public class DataYmlExtension implements TestTemplateInvocationContextProvider {
@Override
public boolean supportsTestTemplate(ExtensionContext extensionContext) {
// 类似于我们写的preHttp,先判断是否能够处理
// 这里判断是否能参数化,能则继续处理
// 这里都是基于反射,拿到测试case
Optional<Method> testMethod = extensionContext.getTestMethod();
// 判断方法上是否有DataYml.class注解
boolean present = testMethod.filter(method -> AnnotationSupport.isAnnotated(method, DataYml.class)).isPresent();
// 这里的filter与isPresent不是流里的提供的功能,是Optional提供的
// 过滤,注解为DataYml.class的注解,存在则返回true
return present;
}
@Override
public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts(ExtensionContext extensionContext) {
// 1、 DataYml 取path
// 2、解析Yml -> T list<T>
// 3、T -> userId pwd
// 4、循环调用
// 通过反射,拿到方法
Method testMethod = extensionContext.getRequiredTestMethod();
// 通过方法,拿到方法上的DataYml注解
DataYml dataYml = testMethod.getAnnotation(DataYml.class);
// 拿到DataYml注解的path
String path = dataYml.path();
List<DataEntity> dataEntities = YmlUtil.read(path);
// System.out.println("path = " + path);
// 返回值 public Stream<TestTemplateInvocationContext>
// TestTemplateInvocationContext 是一个接口
// 我们要实现这个接口,自定义一个类
// 将从yaml读取的数据,转换成这个我们自定义的类
return dataEntities.stream().map(dataEntity -> new DataYmlInvocationContext(dataEntity));
}
// 将从yaml读取的数据,转换成这个我们自定义的类
static class DataYmlInvocationContext implements TestTemplateInvocationContext, ParameterResolver {
private DataEntity dataEntity;
public DataYmlInvocationContext(DataEntity dataEntity){
this.dataEntity = dataEntity;
}
// 实现的TestTemplateInvocationContext这个接口
// 将ParameterResolver的子类 与TestTemplateInvocationContext的子类关联在一起
@Override
public List<Extension> getAdditionalExtensions() {
return Lists.newArrayList(this);
}
// 实现的TestTemplateInvocationContext这个接口
@Override
public String getDisplayName(int invocationIndex) {
return "data provider:"+invocationIndex;
}
// 实现的ParameterResolver这个接口
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
return true;
}
// 实现的ParameterResolver这个接口
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
// 反射
Parameter parameter = parameterContext.getParameter();
String key = parameter.getName();
if(dataEntity.hasKey(key)){
Entity entity = dataEntity.getKey(key);
if(entity.isObject()){
// 如果 entity 是一个对象
// entity.getValue() 是一个json
// 这里将json 转成我们需要的对象,parameter.getType() 这个就是我们需要的对象类型
// 转成这个 parameter.getType()
return new Gson().fromJson(entity.getValue(),parameter.getType());
}
return toJavaTpye(entity.getValue(),parameter.getType());
}
// entity.getValue() 都是字符串
// parameter.getType() 就是在写case时候的传参类型
// public void test1(int userId,String password)
throw new IllegalStateException("no has data");
}
private Object toJavaTpye(String value, Class<?> type) {
// 根绝类型,转换成对应的类型
// entity.getValue() 都是字符串
// parameter.getType() 就是在写case时候的传参类型
// public void test1(int userId,String password)
switch (type.getName()){
case "int":
return Integer.parseInt(value);
case "long":
return Long.parseLong(value);
case "java.lang.String":
return value;
case "double":
return Double.parseDouble(value);
default:
throw new IllegalStateException("unsupport type");
}
}
}
}
七、自定义异常
BaseException
package com.example.autoapi.exception;
public class BaseException extends RuntimeException{
public BaseException() {
}
public BaseException(String message) {
super(message);
}
public BaseException(String message,Object... objects) {
super(String.format(message,objects));
}
public BaseException(String message, Throwable cause) {
super(message, cause);
}
public BaseException(Throwable cause) {
super(cause);
}
public BaseException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
IllegaFormatException
package com.example.autoapi.exception;
public class IllegaFormatException extends BaseException{
public IllegaFormatException() {
}
public IllegaFormatException(String message) {
super(message);
}
public IllegaFormatException(String message, Object... objects) {
super(message, objects);
}
public IllegaFormatException(String message, Throwable cause) {
super(message, cause);
}
public IllegaFormatException(Throwable cause) {
super(cause);
}
public IllegaFormatException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
RequireException
package com.example.autoapi.exception;
public class RequireException extends BaseException{
public RequireException() {
}
public RequireException(String message) {
super(message);
}
public RequireException(String message, Object... objects) {
super(message, objects);
}
public RequireException(String message, Throwable cause) {
super(message, cause);
}
public RequireException(Throwable cause) {
super(cause);
}
public RequireException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
八、封装http请求
这里使用责任链,封装了http请求的方式。
九、报警
钉钉机器人报警的回调。
十、封装JDBC对数据库的连接及操作等
十一、一些实例
十二、测试报告的模版及回调
十三、工具
包括一些反射工具,yml读取工具等