测开工具:二次开发junit接口自动化框架

 一:背景

源码地址: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读取工具等

十四、数据库连接信息及报警模版等

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

做测试的喵酱

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值