fix(接口测试): 处理批量执行失败停止,报告统计

This commit is contained in:
AgAngle 2024-04-03 15:52:01 +08:00 committed by Craftsman
parent 2dcef4ad0c
commit 00ee3a53a3
5 changed files with 175 additions and 61 deletions

View File

@ -2,9 +2,11 @@ package io.metersphere.api.listener;
import io.metersphere.api.event.ApiEventSource; import io.metersphere.api.event.ApiEventSource;
import io.metersphere.api.service.ApiReportSendNoticeService; import io.metersphere.api.service.ApiReportSendNoticeService;
import io.metersphere.api.service.definition.ApiReportService;
import io.metersphere.api.service.definition.ApiTestCaseBatchRunService; import io.metersphere.api.service.definition.ApiTestCaseBatchRunService;
import io.metersphere.api.service.queue.ApiExecutionQueueService; import io.metersphere.api.service.queue.ApiExecutionQueueService;
import io.metersphere.api.service.scenario.ApiScenarioBatchRunService; import io.metersphere.api.service.scenario.ApiScenarioBatchRunService;
import io.metersphere.api.service.scenario.ApiScenarioReportService;
import io.metersphere.sdk.constants.ApiExecuteResourceType; import io.metersphere.sdk.constants.ApiExecuteResourceType;
import io.metersphere.sdk.constants.ApiReportStatus; import io.metersphere.sdk.constants.ApiReportStatus;
import io.metersphere.sdk.constants.ApplicationScope; import io.metersphere.sdk.constants.ApplicationScope;
@ -38,6 +40,10 @@ public class MessageListener {
private ApiTestCaseBatchRunService apiTestCaseBatchRunService; private ApiTestCaseBatchRunService apiTestCaseBatchRunService;
@Resource @Resource
private ApiScenarioBatchRunService apiScenarioBatchRunService; private ApiScenarioBatchRunService apiScenarioBatchRunService;
@Resource
private ApiReportService apiReportService;
@Resource
private ApiScenarioReportService apiScenarioReportService;
@KafkaListener(id = MESSAGE_CONSUME_ID, topics = KafkaTopicConstants.API_REPORT_TASK_TOPIC, groupId = MESSAGE_CONSUME_ID) @KafkaListener(id = MESSAGE_CONSUME_ID, topics = KafkaTopicConstants.API_REPORT_TASK_TOPIC, groupId = MESSAGE_CONSUME_ID)
public void messageConsume(ConsumerRecord<?, String> record) { public void messageConsume(ConsumerRecord<?, String> record) {
@ -78,9 +84,8 @@ public class MessageListener {
} }
ApiExecuteResourceType resourceType = EnumValidator.validateEnum(ApiExecuteResourceType.class, queue.getResourceType()); ApiExecuteResourceType resourceType = EnumValidator.validateEnum(ApiExecuteResourceType.class, queue.getResourceType());
if (BooleanUtils.isTrue(queue.getRunModeConfig().getStopOnFailure()) && StringUtils.equals(dto.getReportStatus(), ApiReportStatus.ERROR.name())) { if (isStopOnFailure(dto, queue, resourceType)) {
// 如果是失败停止清空队列不继续执行 // 失败停止不执行后续任务
apiExecutionQueueService.deleteQueue(queue.getQueueId());
return; return;
} }
@ -95,4 +100,30 @@ public class MessageListener {
LogUtils.error("执行任务失败:", e); LogUtils.error("执行任务失败:", e);
} }
} }
/**
* 处理失败停止后的报告处理
* @param dto
* @param queue
* @param resourceType
* @return
*/
private boolean isStopOnFailure(ApiNoticeDTO dto, ExecutionQueue queue, ApiExecuteResourceType resourceType) {
if (BooleanUtils.isTrue(queue.getRunModeConfig().getStopOnFailure()) && StringUtils.equals(dto.getReportStatus(), ApiReportStatus.ERROR.name())) {
String reportId = queue.getRunModeConfig().isIntegratedReport() ? queue.getRunModeConfig().getCollectionReport().getReportId() : dto.getReportId();
if (resourceType.equals(ApiExecuteResourceType.API_SCENARIO)) {
apiScenarioBatchRunService.UpdateStopOnFailureReport(queue);
}
switch (resourceType) {
case API_CASE -> apiReportService.updateReportStatus(reportId, ApiReportStatus.ERROR.name());
case API_SCENARIO -> apiScenarioReportService.updateReportStatus(reportId, ApiReportStatus.ERROR.name());
default -> {
}
}
// 如果是失败停止清空队列不继续执行
apiExecutionQueueService.deleteQueue(queue.getQueueId());
return true;
}
return false;
}
} }

View File

@ -263,12 +263,12 @@ public class MsScenarioConverter extends AbstractJmeterElementConverter<MsScenar
} }
private void addScenarioProcessor(HashTree tree, MsScenario msScenario, ParameterConfig config, boolean isPre) { private void addScenarioProcessor(HashTree tree, MsScenario msScenario, ParameterConfig config, boolean isPre) {
if (isCopy(msScenario.getRefType())) { // 获取场景前后置
ScenarioConfig scenarioConfig = msScenario.getScenarioConfig();
if (isCopy(msScenario.getRefType()) || scenarioConfig == null) {
// 复制的场景没有前后置 // 复制的场景没有前后置
return; return;
} }
// 获取场景前后置
ScenarioConfig scenarioConfig = msScenario.getScenarioConfig();
MsProcessorConfig processorConfig = isPre ? scenarioConfig.getPreProcessorConfig() : scenarioConfig.getPostProcessorConfig(); MsProcessorConfig processorConfig = isPre ? scenarioConfig.getPreProcessorConfig() : scenarioConfig.getPostProcessorConfig();
if (processorConfig == null || CollectionUtils.isEmpty(processorConfig.getProcessors())) { if (processorConfig == null || CollectionUtils.isEmpty(processorConfig.getProcessors())) {

View File

@ -9,10 +9,8 @@ import io.metersphere.api.dto.ApiScenarioParamConfig;
import io.metersphere.api.dto.ApiScenarioParseTmpParam; import io.metersphere.api.dto.ApiScenarioParseTmpParam;
import io.metersphere.api.dto.debug.ApiResourceRunRequest; import io.metersphere.api.dto.debug.ApiResourceRunRequest;
import io.metersphere.api.dto.request.MsScenario; import io.metersphere.api.dto.request.MsScenario;
import io.metersphere.api.dto.scenario.ApiScenarioBatchRunRequest; import io.metersphere.api.dto.scenario.*;
import io.metersphere.api.dto.scenario.ApiScenarioDetail; import io.metersphere.api.mapper.ApiScenarioReportMapper;
import io.metersphere.api.dto.scenario.ApiScenarioParseParam;
import io.metersphere.api.dto.scenario.ApiScenarioStepDTO;
import io.metersphere.api.mapper.ExtApiScenarioMapper; import io.metersphere.api.mapper.ExtApiScenarioMapper;
import io.metersphere.api.service.ApiBatchRunBaseService; import io.metersphere.api.service.ApiBatchRunBaseService;
import io.metersphere.api.service.ApiExecuteService; import io.metersphere.api.service.ApiExecuteService;
@ -24,10 +22,7 @@ import io.metersphere.sdk.dto.api.task.CollectionReportDTO;
import io.metersphere.sdk.dto.api.task.TaskRequestDTO; import io.metersphere.sdk.dto.api.task.TaskRequestDTO;
import io.metersphere.sdk.dto.queue.ExecutionQueue; import io.metersphere.sdk.dto.queue.ExecutionQueue;
import io.metersphere.sdk.dto.queue.ExecutionQueueDetail; import io.metersphere.sdk.dto.queue.ExecutionQueueDetail;
import io.metersphere.sdk.util.BeanUtils; import io.metersphere.sdk.util.*;
import io.metersphere.sdk.util.DateUtils;
import io.metersphere.sdk.util.LogUtils;
import io.metersphere.sdk.util.SubListUtils;
import io.metersphere.system.uid.IDGenerator; import io.metersphere.system.uid.IDGenerator;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.CollectionUtils;
@ -56,6 +51,8 @@ public class ApiScenarioBatchRunService {
@Resource @Resource
private ApiScenarioReportService apiScenarioReportService; private ApiScenarioReportService apiScenarioReportService;
@Resource @Resource
private ApiScenarioReportMapper apiScenarioReportMapper;
@Resource
private ApiBatchRunBaseService apiBatchRunBaseService; private ApiBatchRunBaseService apiBatchRunBaseService;
@Resource @Resource
private ExtApiScenarioMapper extApiScenarioMapper; private ExtApiScenarioMapper extApiScenarioMapper;
@ -135,40 +132,40 @@ public class ApiScenarioBatchRunService {
// 集成报告执行前先设置成 RUNNING // 集成报告执行前先设置成 RUNNING
setRunningIntegrateReport(runModeConfig); setRunningIntegrateReport(runModeConfig);
AtomicInteger errorCount = new AtomicInteger(); AtomicInteger errorCount = new AtomicInteger();
// 这里ID顺序和队列的ID顺序保持一致 // 这里ID顺序和队列的ID顺序保持一致
for (String id : ids) { for (String id : ids) {
String reportId = null;
try {
ApiScenarioDetail apiScenarioDetail = apiScenarioService.get(id);
if (apiScenarioDetail == null) {
if (runModeConfig.isIntegratedReport()) {
// 用例不存在则在执行集合中删除
apiExecutionSetService.removeItem(runModeConfig.getCollectionReport().getReportId(), id);
}
LogUtils.info("当前执行任务的用例已删除 {}", id);
continue;
}
String reportId = null;
try {
ApiScenarioDetail apiScenarioDetail = apiScenarioService.get(id);
if (apiScenarioDetail == null) {
if (runModeConfig.isIntegratedReport()) { if (runModeConfig.isIntegratedReport()) {
// 集成报告生成虚拟的报告ID // 用例不存在则在执行集合中删除
reportId = IDGenerator.nextStr(); apiExecutionSetService.removeItem(runModeConfig.getCollectionReport().getReportId(), id);
} else {
reportId = scenarioReportMap.get(id);
} }
LogUtils.info("当前执行任务的用例已删除 {}", id);
continue;
}
TaskRequestDTO taskRequest = getTaskRequestDTO(reportId, apiScenarioDetail, runModeConfig); if (runModeConfig.isIntegratedReport()) {
execute(taskRequest, apiScenarioDetail); // 集成报告生成虚拟的报告ID
} catch (Exception e) { reportId = IDGenerator.nextStr();
LogUtils.error("执行用例失败 {}-{}", reportId, id); } else {
LogUtils.error(e); reportId = scenarioReportMap.get(id);
if (errorCount.getAndIncrement() > 10) { }
LogUtils.error("批量执行用例失败错误次数超过10次停止执行");
return; TaskRequestDTO taskRequest = getTaskRequestDTO(reportId, apiScenarioDetail, runModeConfig);
} execute(taskRequest, apiScenarioDetail);
} catch (Exception e) {
LogUtils.error("执行用例失败 {}-{}", reportId, id);
LogUtils.error(e);
if (errorCount.getAndIncrement() > 10) {
LogUtils.error("批量执行用例失败错误次数超过10次停止执行");
return;
} }
} }
}
} }
private Map<String, String> initReport(List<String> ids, ApiRunModeConfigDTO runModeConfig, String userId) { private Map<String, String> initReport(List<String> ids, ApiRunModeConfigDTO runModeConfig, String userId) {
@ -223,7 +220,8 @@ public class ApiScenarioBatchRunService {
} }
/** /**
* 集成报告执行前先设置成 RUNNING * 集成报告执行前先设置成 RUNNING
*
* @param runModeConfig * @param runModeConfig
*/ */
private void setRunningIntegrateReport(ApiRunModeConfigDTO runModeConfig) { private void setRunningIntegrateReport(ApiRunModeConfigDTO runModeConfig) {
@ -366,7 +364,7 @@ public class ApiScenarioBatchRunService {
private ApiScenarioReport getScenarioReport(ApiRunModeConfigDTO runModeConfig, ApiScenario apiScenario, String userId) { private ApiScenarioReport getScenarioReport(ApiRunModeConfigDTO runModeConfig, ApiScenario apiScenario, String userId) {
ApiScenarioReport apiScenarioReport = getScenarioReport(runModeConfig, userId); ApiScenarioReport apiScenarioReport = getScenarioReport(runModeConfig, userId);
apiScenarioReport.setEnvironmentId(getEnvId(runModeConfig, apiScenario)); apiScenarioReport.setEnvironmentId(getEnvId(runModeConfig, apiScenario));
apiScenarioReport.setName(apiScenario.getName() + "_" + DateUtils.getTimeString(System.currentTimeMillis())); apiScenarioReport.setName(apiScenario.getName() + "_" + DateUtils.getTimeString(System.currentTimeMillis()));
apiScenarioReport.setProjectId(apiScenario.getProjectId()); apiScenarioReport.setProjectId(apiScenario.getProjectId());
apiScenarioReport.setTriggerMode(TaskTriggerMode.BATCH.name()); apiScenarioReport.setTriggerMode(TaskTriggerMode.BATCH.name());
return apiScenarioReport; return apiScenarioReport;
@ -398,4 +396,85 @@ public class ApiScenarioBatchRunService {
return StringUtils.isBlank(runModeConfig.getEnvironmentId()) ? apiScenario.getGrouped() : runModeConfig.getGrouped(); return StringUtils.isBlank(runModeConfig.getEnvironmentId()) ? apiScenario.getGrouped() : runModeConfig.getGrouped();
} }
public void UpdateStopOnFailureReport(ExecutionQueue queue) {
ApiRunModeConfigDTO runModeConfig = queue.getRunModeConfig();
try {
ExecutionQueueDetail queueDetail = apiExecutionQueueService.getNextDetail(queue.getQueueId());
if (queueDetail == null) {
return;
}
Long requestCount = 0L;
while (queueDetail != null) {
ApiScenarioDetail apiScenarioDetail = apiScenarioService.get(queueDetail.getResourceId());
if (apiScenarioDetail == null) {
LogUtils.info("当前场景已删除 {}", queueDetail.getResourceId());
continue;
}
Long requestCountItem = getRequestCount(apiScenarioDetail.getSteps());
requestCount += requestCountItem;
// 初始化报告步骤
if (runModeConfig.isIntegratedReport()) {
apiScenarioService.initScenarioReportSteps(apiScenarioDetail.getId(), apiScenarioDetail.getSteps(), runModeConfig.getCollectionReport().getReportId());
} else {
apiScenarioService.initScenarioReportSteps(apiScenarioDetail.getSteps(), queueDetail.getReportId());
}
queueDetail = apiExecutionQueueService.getNextDetail(queue.getQueueId());
}
if (runModeConfig.isIntegratedReport()) {
// 获取未执行的请求数更新统计指标
String reportId = runModeConfig.getCollectionReport().getReportId();
ApiScenarioReport report = apiScenarioReportMapper.selectByPrimaryKey(reportId);
Long pendingCount = requestCount + report.getPendingCount();
report.setPendingCount(pendingCount);
// 计算各种通过率
report = computeRequestRate(report);
apiScenarioReportMapper.updateByPrimaryKeySelective(report);
}
} catch (Exception e) {
LogUtils.error("失败停止,补充报告步骤失败:", e);
}
}
public ApiScenarioReport computeRequestRate(ApiScenarioReport report) {
long total = apiScenarioReportService.getRequestTotal(report);
// 计算各个概率
double successRate = calculateRate(report.getSuccessCount(), total);
double errorRate = calculateRate(report.getErrorCount(), total);
double pendingRate = calculateRate(report.getPendingCount(), total);
double fakeErrorRate = calculateRate(report.getFakeErrorCount(), total);
// 计算总和
double sum = successRate + errorRate + pendingRate + fakeErrorRate;
LogUtils.info("偏移总量重新计算", sum);
// 避免分母为零
double adjustment = sum > 0 ? 1.0 / sum : 0.0;
// 调整概率使总和精确为100%
successRate *= adjustment;
errorRate *= adjustment;
pendingRate *= adjustment;
fakeErrorRate *= adjustment;
report.setRequestPassRate(formatRate(successRate));
report.setRequestErrorRate(formatRate(errorRate));
report.setRequestPendingRate(formatRate(pendingRate));
report.setRequestFakeErrorRate(formatRate(fakeErrorRate));
return report;
}
// 计算概率
private static double calculateRate(long count, double total) {
return total > 0 ? count / total : 0.0;
}
// 格式化概率保留两位小数
private static String formatRate(double rate) {
return String.format("%.2f", rate * 100);
}
} }

View File

@ -184,9 +184,6 @@ public class ApiScenarioReportService {
BeanUtils.copyBean(scenarioReportDTO, scenarioReport); BeanUtils.copyBean(scenarioReportDTO, scenarioReport);
//需要查询出所有的步骤 //需要查询出所有的步骤
List<ApiScenarioReportStepDTO> scenarioReportSteps = extApiScenarioReportMapper.selectStepByReportId(id); List<ApiScenarioReportStepDTO> scenarioReportSteps = extApiScenarioReportMapper.selectStepByReportId(id);
if (CollectionUtils.isEmpty(scenarioReportSteps)) {
throw new MSException(Translator.get("api_scenario_report_not_exist"));
}
if (BooleanUtils.isFalse(scenarioReport.getIntegrated())) { if (BooleanUtils.isFalse(scenarioReport.getIntegrated())) {
ApiScenarioBlob apiScenarioBlob = extApiScenarioReportMapper.getScenarioBlob(id); ApiScenarioBlob apiScenarioBlob = extApiScenarioReportMapper.getScenarioBlob(id);
if (apiScenarioBlob != null) { if (apiScenarioBlob != null) {
@ -236,12 +233,12 @@ public class ApiScenarioReportService {
//将scenarioReportSteps按照parentId进行分组 值为list 然后根据sort进行排序 //将scenarioReportSteps按照parentId进行分组 值为list 然后根据sort进行排序
Map<String, List<ApiScenarioReportStepDTO>> scenarioReportStepMap = scenarioReportSteps.stream().collect(Collectors.groupingBy(ApiScenarioReportStepDTO::getParentId)); Map<String, List<ApiScenarioReportStepDTO>> scenarioReportStepMap = scenarioReportSteps.stream().collect(Collectors.groupingBy(ApiScenarioReportStepDTO::getParentId));
// TODO 查询修改 // TODO 查询修改
List<ApiScenarioReportStepDTO> steps = scenarioReportStepMap.get("NONE"); List<ApiScenarioReportStepDTO> steps = Optional.ofNullable(scenarioReportStepMap.get("NONE")).orElse(new ArrayList<>(0));
steps.sort(Comparator.comparingLong(ApiScenarioReportStepDTO::getSort)); steps.sort(Comparator.comparingLong(ApiScenarioReportStepDTO::getSort));
getStepTree(steps, scenarioReportStepMap); getStepTree(steps, scenarioReportStepMap);
scenarioReportDTO.setStepTotal(steps.size()); scenarioReportDTO.setStepTotal(steps.size());
scenarioReportDTO.setRequestTotal(scenarioReportDTO.getErrorCount() + scenarioReportDTO.getPendingCount() + scenarioReportDTO.getSuccessCount() + scenarioReportDTO.getFakeErrorCount()); scenarioReportDTO.setRequestTotal(getRequestTotal(scenarioReportDTO));
scenarioReportDTO.setChildren(steps); scenarioReportDTO.setChildren(steps);
scenarioReportDTO.setStepErrorCount(steps.stream().filter(step -> StringUtils.equals(ApiReportStatus.ERROR.name(), step.getStatus())).count()); scenarioReportDTO.setStepErrorCount(steps.stream().filter(step -> StringUtils.equals(ApiReportStatus.ERROR.name(), step.getStatus())).count());
@ -274,6 +271,10 @@ public class ApiScenarioReportService {
return scenarioReportDTO; return scenarioReportDTO;
} }
public long getRequestTotal(ApiScenarioReport report) {
return report.getErrorCount() + report.getPendingCount() + report.getSuccessCount() + report.getFakeErrorCount();
}
private static void getStepTree(List<ApiScenarioReportStepDTO> steps, Map<String, List<ApiScenarioReportStepDTO>> scenarioReportStepMap) { private static void getStepTree(List<ApiScenarioReportStepDTO> steps, Map<String, List<ApiScenarioReportStepDTO>> scenarioReportStepMap) {
if (CollectionUtils.isNotEmpty(steps)) { if (CollectionUtils.isNotEmpty(steps)) {
List<String> stepTypes = Arrays.asList(ApiScenarioStepType.IF_CONTROLLER.name(), List<String> stepTypes = Arrays.asList(ApiScenarioStepType.IF_CONTROLLER.name(),

View File

@ -1710,19 +1710,22 @@ public class ApiScenarioService extends MoveNodeService {
// 获取当前场景配置的环境信息 // 获取当前场景配置的环境信息
EnvironmentModeDTO environmentModeDTO = scenarioParseEnvInfo.getRefScenarioEnvMap().get(step.getResourceId()); EnvironmentModeDTO environmentModeDTO = scenarioParseEnvInfo.getRefScenarioEnvMap().get(step.getResourceId());
String environmentId = environmentModeDTO.getEnvironmentId();
// 设置是否是环境组 if (environmentModeDTO != null) {
Boolean isGrouped = environmentModeDTO.getGrouped(); String environmentId = environmentModeDTO.getEnvironmentId();
msScenario.setGrouped(isGrouped);
Map<String, EnvironmentInfoDTO> envMap = scenarioParseEnvInfo.getEnvMap();
if (BooleanUtils.isTrue(isGrouped)) { // 设置是否是环境组
// 设置环境组 map Boolean isGrouped = environmentModeDTO.getGrouped();
msScenario.setProjectEnvMap(getProjectEnvMap(scenarioParseEnvInfo, environmentId)); msScenario.setGrouped(isGrouped);
} else { Map<String, EnvironmentInfoDTO> envMap = scenarioParseEnvInfo.getEnvMap();
// 设置环境
msScenario.setEnvironmentInfo(envMap.get(environmentId)); if (BooleanUtils.isTrue(isGrouped)) {
// 设置环境组 map
msScenario.setProjectEnvMap(getProjectEnvMap(scenarioParseEnvInfo, environmentId));
} else {
// 设置环境
msScenario.setEnvironmentInfo(envMap.get(environmentId));
}
} }
} }
@ -2151,7 +2154,7 @@ public class ApiScenarioService extends MoveNodeService {
} }
apiCommonService.setEnableCommonScriptProcessorInfo(msTestElement); apiCommonService.setEnableCommonScriptProcessorInfo(msTestElement);
} }
return stepDetail; return JSON.parseObject(JSON.toJSONString(stepDetail));
} }
private void checkTargetModule(String targetModuleId, String projectId) { private void checkTargetModule(String targetModuleId, String projectId) {