天天看点

八、junit接口自动化框架-钉钉发送报告

思路:

执行完case后,通过钉钉来发送运行case的结果。也是通过打标签的方式实现。

一、注解 ReportConfig

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";

}      

我们通过注解来实现发送报告的功能。

case运行入口如下:

public class SelectCaseRun {
    @CaseSelector(scanPackage = "com.example.autoapi.cases.login",key="level",val="redLine")
    @DingTalkAlarm(token="99b4d8ca6a56739f0a5e08bf5b816735567ecab59a323d00c35a8473b4e6a07f",alarmCallBack = DefaultAlarmCallBack.class)
    @ReportConfig(token="99b4d8ca6a56739f0a5e08bf5b816735567ecab59a323d00c35a8473b4e6a07f",callback = DefaultReportCallBack.class)
    public void redLine(){

    }
}      

1、token是给钉钉用的,发消息的时候用到token

2、回调,callback,具体发送报告的方式。因为发送报告有多种形式,比如微信发、钉钉发

、邮件发,把具体的发送方式通过这个传进来

3、依然采用的是模版的形式,这里给一个默认模版

String template() default "default_report_template";      

二、报告模版 default_report_template

-----------用例运行结果:报告-----------
成功数:${success_count}
失败数:${failure_count}
总数:${total_count}
开始时间:${start_time}
结束时间:${end_time}
失败原因:${failure_msg}      

三、发送报告的回调

ReportCallBack

package com.example.autoapi.report.callback;

import com.example.autoapi.model.SummaryResult;

public interface ReportCallBack {
    void postReport(SummaryResult summaryResult,String token,String templateKey);
}      

DefaultReportCallBack 默认发送报告的方式、是钉钉发送报告。

package com.example.autoapi.report.callback;

import com.example.autoapi.http.HttpFacade;
import com.example.autoapi.model.SummaryResult;
import com.example.autoapi.template.TemplateFacade;

import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

public class DefaultReportCallBack implements ReportCallBack{
    @Override
    public void postReport(SummaryResult summaryResult,String token,String templateKey) {
        // 将所有的报错信息组成一个字符串
        String msgs = summaryResult.getFailureResults().stream()
                .map(failureResult -> failureResult.getThrowable().getMessage())
                .collect(Collectors.joining("\n"));

        Map<String,Object> map = new HashMap<>();
        map.put("success_count",summaryResult.getSuccessCount());
        map.put("failure_count",summaryResult.getFailureCount());
        map.put("total_count",summaryResult.getTotalCount());
        map.put("start_time",summaryResult.getStartTime());
        map.put("end_time",summaryResult.getEndTime());
        map.put("failure_msg",msgs);
        String template = TemplateFacade.replaceTemplate(templateKey,map);
        Map<String,String> text = new HashMap<>();
        text.put("content",template);
        Map<String,Object> data = new HashMap<>();
        data.put("text",text);
        data.put("msgtype","text");
        String url = DING_ALARM_URL + token;
        HttpFacade.doPostJson(url,data);



    }
    private final static String DING_ALARM_URL = "https://oapi.dingtalk.com/robot/send?access_token=";

}      

1、收集运行结果的数据,储存在一个类里

2、将运行数据组成Map,读取模版,替换模版

3、调用发送请求的接口,发送请求

四、CaseSelectorExtension

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");


    }


}      

1、这个是筛选,运行case的逻辑,最后运行完得到了运行结果

2、判断是否有发送报告的注解

3、将运行结果储存在我们自己定义的储存结果的类,这里之所以不直接用官方的,是为了以后扩展用

ReportConfig reportConfig = method.getAnnotation(ReportConfig.class);
            ReflectUtils.newInstance(reportConfig.callback()).postReport(summaryResult, reportConfig.token(), reportConfig.template());      

五、自定义储存运行结果的类SummaryResult 

package com.example.autoapi.model;

import lombok.Builder;
import lombok.Data;

import java.util.List;

@Data
@Builder
public class SummaryResult {
    private long totalCount;
    private long successCount;
    private long failureCount;
    private long startTime;
    private long endTime;
    private List<FailureResult> failureResults;
}