思路:
执行完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;
}