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

news2024/12/25 12:45:25

 一:背景

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/115629.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何使用depcheck检查依赖?

目录 1. 你是否遇到过以下问题&#xff1a; 2. 原因 3. 解决 4. depcheck的其他功能 5.需要说明可能存在的问题 总结 1. 你是否遇到过以下问题&#xff1a; 1. GitHub上面克隆的项目&#xff0c;运行报错… 2.去一家新公司&#xff0c;用新电脑运行项目各种报错… 3.明…

有关linux中的文件IO的操作

Linux 应用编程中最需要掌握的基础就是文件 I/O的操作&#xff0c;学习过linux或者有过了解的应该都会听过一句话&#xff1a;linux中一切皆文件&#xff0c;文件是linux系统的核心设计思想。所以掌握文件的操作是很重要的。 那文件 I/O 又是什么&#xff1f;文件I/O指的是对文…

NumPy transpose 的定义与计算过程

NumPy transpose 的定义与计算过程NumPy documentation https://numpy.org/doc/stable/index.html numpy.transpose https://numpy.org/doc/stable/reference/generated/numpy.transpose.html numpy.ndarray.transpose https://numpy.org/doc/stable/reference/generated/num…

π120M31 10Mbps双通道数字隔离器兼容代替ADuM1200BR

π120M31 10Mbps双通道数字隔离器兼容代替ADuM1200BR 。具有出色的性能特征和可靠性&#xff0c;整体性能优于光耦和基于其他原理的数字隔离器产品。传输通道间彼此独立&#xff0c;可实现多种传输方向的配置&#xff0c;可实现 3.0kV rms 隔离耐压等级和 DC 到 10Mbps 信号传输…

网络通信基础

2022年圣诞节到来啦&#xff0c;很高兴这次我们又能一起度过~ 祝大家圣诞快乐&#x1f384; 网络互连的目的是进行网络通信, 也即是网络数据传输, 再具体一点, 是网络主机中的不同进程间, 基于网络传输数据. 那么, 在组建的网络中, 如何判断到底是从哪台主机, 将数据传输到那…

JAVA物联网云平台源码/Modbus/视频接入 物联网智能看板源码

JAVA物联网云平台使用 Java 语言、主流技术组合&#xff08;MQTT、Spring Boot、Shiro、MyBatis、Druid、Ehcache、Thymeleaf、Bootstrap、Swagger&#xff09;开发&#xff0c;支持多数据源&#xff0c;支持代码一键生成。 1、包含物联网云平台源码&#xff08;源码全部开放&a…

【Three.js入门】灯光与阴影、平行光阴影属性、聚光灯的属性和应用

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;也会涉及到服务端 &#x1f4c3;个人状态&#xff1a; 在校大学生一枚&#xff0c;已拿多个前端 offer&#xff08;秋招&#xff09; &#x1f680;未…

百趣代谢组学分享:针灸改善乳腺癌相关性疲劳!非靶代谢组学…

非靶代谢组学肠道菌群测序&#xff1a;改善乳腺癌相关性疲劳—针灸! 文章标题&#xff1a;Acupuncture ameliorates breast cancer-related fatigue by regulating the gut microbiota-gut-brain axis 发表期刊&#xff1a;Frontiers in Endocrinology 影响因子&#xff1…

HTML5 新增元素

文章目录HTML5 新增元素概述headernavarticleasidesectionfooter表单元素新增type属性值&#xff08;验证型&#xff09;新增type属性值&#xff08;取值型&#xff09;output元素addressprogressmeterfigure 和 figcaptionfieldset 和 legendascriptHTML5 新增元素 概述 HTM…

BOM对象

1、什么是BOM BOM&#xff08;Browser Object Model&#xff09;即浏览器对象模型&#xff0c;它提供了独立于内容而与浏览器窗口进行交互的对象&#xff0c;其核心对象是 window。 BOM 由一系列相关的对象构成&#xff0c;并且每个对象都提供了很多方法与属性。 BOM 缺乏标…

B端产品-筛选功能如何设计

相信大家在设计B端产品时&#xff0c;最常打交道的就是列表&#xff0c;而列表最常用的一个功能就是搜索&#xff0c;别小看一个简简单单的搜索功能&#xff0c;其实里边的门道也是有很多的。那么如何设计一个好用的搜索栏呢&#xff1f;今天就与大家分享一下&#xff0c;自己负…

【动态规划篇】最少分割回文 编辑距离 不同的子序列

&#x1f320;作者&#xff1a;阿亮joy. &#x1f386;专栏&#xff1a;《数据结构与算法要啸着学》 &#x1f387;座右铭&#xff1a;每个优秀的人都有一段沉默的时光&#xff0c;那段时光是付出了很多努力却得不到结果的日子&#xff0c;我们把它叫做扎根 目录&#x1f449;…

音视频技术开发周刊 | 277

每周一期&#xff0c;纵览音视频技术领域的干货。新闻投稿&#xff1a;contributelivevideostack.com。GNNear&#xff1a;基于近内存处理的大规模图神经网络训练加速器图神经网络(GNNs)已经成为分析非欧几里得图数据的最新算法。然而&#xff0c;实现高效的GNN训练是一项挑战&…

西门子1513CPU冗余组态

西门子1513R冗余配置 1、打开博图16软件&#xff0c;新建项目&#xff0c;添加新设备&#xff0c;在弹出的控制器目录下找到CPU 1513R-1 PN硬件&#xff0c;然后系统自动生成PLC_1和PLC_2&#xff1b; 2、打开设备组态中的网络视图&#xff0c;添加接口模块&#xff1b; 3、在…

Springboot 统计 代码执行耗时时间 ,玩法多到眼花

前言 近日群里有萌新提到关于统计代码执行时间的事&#xff1a; 开始 System.currentTimeMillis() 减去 结束 System.currentTimeMillis() 等于 耗时 其实我个人感觉OK的&#xff0c;就这样就蛮好的&#xff0c;很多项目都是这样用的。 简简单单的挺好。 这一篇就是 …

基于Java( jsp+servlet+javabean)+SQL sever 2017实现(Web)高校选课管理系统【100010058】

一、需求分析 开发意义&#xff1a; 随着信息技术不断向深入发展&#xff0c;越来越多的学校开始着手信息化建设。其中学生选课、成绩信息化管理就是其中重要的一块内容。学生选课与成绩信息规模大、项目条数多、信息量庞大&#xff0c;传统的人工管理方式显然已经无法满足要求…

买不到的数目(蓝桥杯C/C++A组真题详解)

题目详细&#xff1a; 题目思路&#xff1a; 对于这个题有一个定理 如果 a,b 均是正整数且互质&#xff0c;那么由 axby&#xff0c;x≥0&#xff0c;y≥0 不能凑出的最大数是 &#xff1a; a*b-a-b 具体的证明过程这里就不赘述 感兴趣的同学可以自行查找 这里就提供一种思…

rk3568 | rk平台GPIO冲突检测小技巧

上一篇我们讲解了如何编写gpio驱动&#xff0c;但是实际操作中&#xff0c;经常发现gpio引脚被占用的情况发生&#xff0c;那么本篇文章就详细讲解rxw平台下如何快速定位gpio复用问题以及如何解决。 一、GPIO寄存器查找 要想查看某个GPIO引脚可以配置的功能以及地址信息&…

Http协议基础

Http 是什么&#xff1f; Http 作为 WWW 的重要组成部分被提出 (World Wide Web) 而当时的 WWW 是为了帮助研究者更好的进行知识共享 基本理念&#xff1a;文档之间的相互关联形成超文本&#xff0c;最终连成相互参阅的 WWW Http (Hyper Text Transfer Protocol) 用于定义文…

QUIC的优势与缺陷

被寄予厚望的下一代互联网传输协议&#xff0c;QUIC究竟有哪些优点呢&#xff1f; 总结如下&#xff1a; 多路复用&#xff1a;QUIC升华了HTTP/2中的多路复用技术&#xff0c;实现了基于互相独立的多流&#xff08;多通道&#xff09;数据传输&#xff0c;从根本上解决了TCP存在…