feat(测试跟踪): 测试计划增加执行成功和失败的消息通知功能

测试计划增加执行成功和失败的消息通知功能
This commit is contained in:
song-tianyang 2022-04-14 15:10:42 +08:00 committed by TIanyang
parent 09f4d7057e
commit 6207c72d70
9 changed files with 149 additions and 133 deletions

View File

@ -23,6 +23,7 @@ import io.metersphere.utils.LoggerUtil;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@ -55,10 +56,9 @@ public class ApiExecutionQueueService {
private ExtApiExecutionQueueMapper extApiExecutionQueueMapper;
@Resource
private ApiScenarioReportResultMapper apiScenarioReportResultMapper;
@Lazy
@Resource
private TestPlanExecutionQueueMapper testPlanExecutionQueueMapper;
@Resource
private TestPlanReportMapper testPlanReportMapper;
private TestPlanReportService testPlanReportService;
@Transactional(propagation = Propagation.REQUIRES_NEW)
@ -92,6 +92,22 @@ public class ApiExecutionQueueService {
queueDetails.add(queue);
detailMap.put(k, queue.getId());
});
} else if (StringUtils.equalsIgnoreCase(type, ApiRunMode.TEST_PLAN_PERFORMANCE_TEST.name())) {
final int[] sort = {0};
Map<String, String> runMap = (Map<String, String>) runObj;
if (config.getEnvMap() == null) {
config.setEnvMap(new LinkedHashMap<>());
}
String envStr = JSON.toJSONString(config.getEnvMap());
runMap.forEach((k, v) -> {
ApiExecutionQueueDetail queue = detail(v, k, "loadTest", sort[0], executionQueue.getId(), envStr);
if (sort[0] == 0) {
resQueue.setQueue(queue);
}
sort[0]++;
queueDetails.add(queue);
detailMap.put(k, queue.getId());
});
} else {
Map<String, RunModeDataDTO> runMap = (Map<String, RunModeDataDTO>) runObj;
final int[] sort = {0};
@ -304,7 +320,7 @@ public class ApiExecutionQueueService {
// 计算一小时前的超时报告
final long timeout = System.currentTimeMillis() - (60 * MINUTE_MILLIS);
ApiExecutionQueueDetailExample example = new ApiExecutionQueueDetailExample();
example.createCriteria().andCreateTimeLessThan(timeout);
example.createCriteria().andCreateTimeLessThan(timeout).andTypeNotEqualTo("loadTest");
List<ApiExecutionQueueDetail> queueDetails = executionQueueDetailMapper.selectByExample(example);
for (ApiExecutionQueueDetail item : queueDetails) {
@ -416,4 +432,24 @@ public class ApiExecutionQueueService {
}
});
}
public void checkExecutionQueneByLoadTest(LoadTestReport loadTestReport) {
ApiExecutionQueueDetailExample detailExample = new ApiExecutionQueueDetailExample();
detailExample.createCriteria().andReportIdEqualTo(loadTestReport.getId());
List<ApiExecutionQueueDetail> detailList = executionQueueDetailMapper.selectByExample(detailExample);
if (CollectionUtils.isNotEmpty(detailList)) {
List<String> executionQueueIdList = new ArrayList<>();
detailList.forEach(item -> {
executionQueueIdList.add(item.getQueueId());
});
executionQueueDetailMapper.deleteByExample(detailExample);
}
List<String> testPlanReportIdList = testPlanReportService.getTestPlanReportIdsByLoadTestReportId(loadTestReport.getId());
for (String testPlanReportId : testPlanReportIdList) {
this.testPlanReportTestEnded(testPlanReportId);
}
}
}

View File

@ -317,9 +317,8 @@
<select id="findIdByPerformanceReportId" resultType="java.lang.String">
SELECT report.id
FROM test_plan_report report
INNER JOIN test_plan_report_data reportData ON report.id = reportData.test_plan_report_id
WHERE reportData.performance_info like CONCAT('%', #{0}, '%')
AND report.is_performance_executing = true;
INNER JOIN test_plan_report_content reportData ON report.id = reportData.test_plan_report_id
WHERE reportData.plan_load_case_report_struct like CONCAT('%', #{0}, '%');
</select>
<select id="listRecent" resultType="io.metersphere.base.domain.TestPlan">
select distinct test_plan.*

View File

@ -2,6 +2,7 @@ package io.metersphere.commons.constants;
public enum ApiRunMode {
RUN, DEBUG, DEFINITION, TEST_CASE, SCENARIO, API_PLAN, JENKINS_API_PLAN, JENKINS_SCENARIO_PLAN, JENKINS_PERFORMANCE_TEST, JENKINS,
TEST_PLAN_PERFORMANCE_TEST,
SCENARIO_PLAN, API, SCHEDULE_API_PLAN, SCHEDULE_SCENARIO, SCHEDULE_SCENARIO_PLAN, SCHEDULE_PERFORMANCE_TEST, MANUAL_PLAN,
UI_SCENARIO, UI_SCENARIO_PLAN, UI_SCHEDULE_SCENARIO_PLAN, UI_JENKINS_SCENARIO_PLAN, UI_SCHEDULE_SCENARIO
}

View File

@ -2,16 +2,17 @@ package io.metersphere.commons.consumer;
import com.alibaba.fastjson.JSON;
import io.metersphere.Application;
import io.metersphere.api.service.ApiExecutionQueueService;
import io.metersphere.base.domain.LoadTestReport;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.config.KafkaProperties;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.reflections8.Reflections;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.Set;
@Service
@ -19,6 +20,9 @@ import java.util.Set;
public class LoadTestConsumer {
public static final String CONSUME_ID = "load-test-data";
@Resource
ApiExecutionQueueService apiExecutionQueueService;
@KafkaListener(id = CONSUME_ID, topics = "${kafka.test.topic}", groupId = "${spring.kafka.consumer.group-id}")
public void consume(ConsumerRecord<?, String> record) {
LoadTestReport loadTestReport = JSON.parseObject(record.value(), LoadTestReport.class);
@ -31,5 +35,7 @@ public class LoadTestConsumer {
LogUtil.error(e);
}
});
//删除性能测试在执行队列中的数据 在测试计划执行中会将性能测试执行添加到执行队列用于判断整个测试计划到执行进度
apiExecutionQueueService.checkExecutionQueneByLoadTest(loadTestReport);
}
}

View File

@ -126,19 +126,7 @@ public class NoticeSendService {
public void send(Project project, String taskType, NoticeModel noticeModel) {
try {
List<MessageDetail> messageDetails;
// switch (taskType) {
// case NoticeConstants.Mode.API:
// String projectId = (String) noticeModel.getParamMap().get("projectId");
// messageDetails = noticeService.searchMessageByTypeBySend(NoticeConstants.TaskType.JENKINS_TASK, projectId);
// break;
// case NoticeConstants.Mode.SCHEDULE:
// messageDetails = noticeService.searchMessageByTestId(noticeModel.getTestId());
// break;
// default:
// break;
// }
messageDetails = noticeService.searchMessageByTypeAndProjectId(taskType, project.getId());
// 异步发送通知
messageDetails.stream()
.filter(messageDetail -> StringUtils.equals(messageDetail.getEvent(), noticeModel.getEvent()))

View File

@ -1,39 +0,0 @@
package io.metersphere.track.service;
import io.metersphere.base.domain.LoadTestReport;
import io.metersphere.base.domain.TestPlanReport;
import io.metersphere.commons.constants.NoticeConstants;
import io.metersphere.commons.constants.PerformanceTestStatus;
import io.metersphere.commons.constants.ReportTriggerMode;
import io.metersphere.commons.consumer.LoadTestFinishEvent;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.track.dto.TestPlanLoadCaseEventDTO;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
/**
* @author song.tianyang
* @Date 2021/1/13 2:53 下午
* @Description
*/
@Service
@Transactional(rollbackFor = Exception.class)
public class TestPlanLoadCaseEvent implements LoadTestFinishEvent {
@Resource
TestPlanReportService testPlanReportService;
@Override
public void execute(LoadTestReport loadTestReport) {
LogUtil.info("PerformanceNoticeEvent OVER:" + loadTestReport.getTriggerMode()+";"+loadTestReport.getStatus());
if (StringUtils.equals(ReportTriggerMode.TEST_PLAN_SCHEDULE.name(), loadTestReport.getTriggerMode()) ) {
TestPlanLoadCaseEventDTO eventDTO = new TestPlanLoadCaseEventDTO();
eventDTO.setReportId(loadTestReport.getId());
eventDTO.setTriggerMode(ReportTriggerMode.SCHEDULE.name());
eventDTO.setStatus(loadTestReport.getStatus());
testPlanReportService.updatePerformanceTestStatus(eventDTO);
}
}
}

View File

@ -448,8 +448,6 @@ public class TestPlanReportService {
//初始化测试计划包含组件信息
int[] componentIndexArr = new int[]{1, 3, 4};
testPlanReport.setComponents(JSONArray.toJSONString(componentIndexArr));
//计算测试计划状态
testPlanReport.setStatus(status);
//如果测试案例没有未结束的功能用例则更新最后结束日期
TestPlanTestCaseMapper testPlanTestCaseMapper = CommonBeanFactory.getBean(TestPlanTestCaseMapper.class);
TestPlanTestCaseExample testPlanTestCaseExample = new TestPlanTestCaseExample();
@ -469,6 +467,7 @@ public class TestPlanReportService {
if(CollectionUtils.isNotEmpty(contents)){
content = contents.get(0);
}
boolean hasErrorCase = false;
if(content!= null){
//更新接口用例场景用例的最终执行状态
if(StringUtils.isNotEmpty(content.getPlanApiCaseReportStruct())){
@ -493,6 +492,9 @@ public class TestPlanReportService {
}
dto.setExecResult(execStatus);
}
if(!StringUtils.equalsAnyIgnoreCase( dto.getExecResult(),"success")){
hasErrorCase = true;
}
}
content.setPlanApiCaseReportStruct(JSONObject.toJSONString(apiTestCases));
}catch (Exception e){
@ -524,6 +526,9 @@ public class TestPlanReportService {
}
dto.setLastResult(execStatus);
dto.setStatus(execStatus);
if(!StringUtils.equalsAnyIgnoreCase(execStatus,"success")){
hasErrorCase = true;
}
}
}
content.setPlanScenarioReportStruct(JSONObject.toJSONString(scenarioCases));
@ -536,7 +541,16 @@ public class TestPlanReportService {
content.setEndTime(endTime);
testPlanReportContentMapper.updateByExampleSelective(content, contentExample);
}
//计算测试计划状态
if(StringUtils.equalsIgnoreCase(status,TestPlanReportStatus.COMPLETED.name())){
if(hasErrorCase){
testPlanReport.setStatus(TestPlanReportStatus.FAILED.name());
}else {
testPlanReport.setStatus(TestPlanReportStatus.SUCCESS.name());
}
}else {
testPlanReport.setStatus(status);
}
//更新测试计划并发送通知
testPlanReport.setIsApiCaseExecuting(false);
testPlanReport.setIsScenarioExecuting(false);
@ -759,7 +773,6 @@ public class TestPlanReportService {
BaseSystemConfigDTO baseSystemConfigDTO = systemParameterService.getBaseInfo();
String url = baseSystemConfigDTO.getUrl() + "/#/track/testPlan/reportList";
String subject = "";
String event = NoticeConstants.Event.COMPLETE;
String successContext = "${operator}执行的 ${name} 测试计划运行成功, 报告: ${planShareUrl}";
String failedContext = "${operator}执行的 ${name} 测试计划运行失败, 报告: ${planShareUrl}";
String context = "${operator}完成了测试计划: ${name}, 报告: ${planShareUrl}";
@ -790,6 +803,22 @@ public class TestPlanReportService {
String testPlanShareUrl = shareInfoService.getTestPlanShareUrl(testPlanReport.getId(), creator);
paramMap.put("planShareUrl", baseSystemConfigDTO.getUrl() + "/sharePlanReport" + testPlanShareUrl);
/**
* 测试计划的消息通知配置包括 完成成功失败
* 所以发送通知时必定会有"完成"状态的通知
*/
Map<String,String> execStatusEventMap = new HashMap<>();
execStatusEventMap.put(TestPlanReportStatus.COMPLETED.name(),NoticeConstants.Event.COMPLETE);
if(StringUtils.equalsIgnoreCase(testPlanReport.getStatus(),TestPlanReportStatus.SUCCESS.name())){
execStatusEventMap.put(testPlanReport.getStatus(),NoticeConstants.Event.EXECUTE_SUCCESSFUL);
}else if(StringUtils.equalsIgnoreCase(testPlanReport.getStatus(),TestPlanReportStatus.FAILED.name())){
execStatusEventMap.put(testPlanReport.getStatus(),NoticeConstants.Event.EXECUTE_FAILED);
}else if(!StringUtils.equalsIgnoreCase(testPlanReport.getStatus(),TestPlanReportStatus.COMPLETED.name())){
execStatusEventMap.put(testPlanReport.getStatus(),NoticeConstants.Event.COMPLETE);
}
for (Map.Entry<String,String> entry : execStatusEventMap.entrySet()) {
String status = entry.getKey();
String event = entry.getValue();
NoticeModel noticeModel = NoticeModel.builder()
.operator(creator)
.context(context)
@ -799,7 +828,7 @@ public class TestPlanReportService {
.failedMailTemplate(errfoMailTemplate)
.mailTemplate("track/TestPlanComplete")
.testId(testPlan.getId())
.status(testPlanReport.getStatus())
.status(status)
.event(event)
.subject(subject)
.paramMap(paramMap)
@ -813,18 +842,15 @@ public class TestPlanReportService {
noticeSendService.send(testPlanReport.getTriggerMode(), NoticeConstants.TaskType.TEST_PLAN_TASK, noticeModel);
}
}
}
public TestPlanReport getTestPlanReport(String planId) {
return testPlanReportMapper.selectByPrimaryKey(planId);
}
public void updatePerformanceTestStatus(TestPlanLoadCaseEventDTO eventDTO) {
List<String> testPlanReportId = extTestPlanMapper.findIdByPerformanceReportId(eventDTO.getReportId());
if (StringUtils.equals(eventDTO.getTriggerMode(), ReportTriggerMode.API.name())) {
this.updateReport(testPlanReportId, ApiRunMode.JENKINS_PERFORMANCE_TEST.name(), eventDTO.getTriggerMode());
} else {
this.updateReport(testPlanReportId, ApiRunMode.SCHEDULE_PERFORMANCE_TEST.name(), eventDTO.getTriggerMode());
}
public List<String> getTestPlanReportIdsByLoadTestReportId(String loadTestReportId) {
List<String> testPlanReportId = extTestPlanMapper.findIdByPerformanceReportId(loadTestReportId);
return testPlanReportId;
}
public void delete(List<String> testPlanReportIdList) {

View File

@ -13,10 +13,7 @@ import io.metersphere.api.dto.definition.ApiTestCaseRequest;
import io.metersphere.api.dto.definition.BatchRunDefinitionRequest;
import io.metersphere.api.dto.definition.ParamsDTO;
import io.metersphere.api.dto.definition.TestPlanApiCaseDTO;
import io.metersphere.api.service.ApiAutomationService;
import io.metersphere.api.service.ApiDefinitionService;
import io.metersphere.api.service.ApiScenarioReportService;
import io.metersphere.api.service.ApiTestCaseService;
import io.metersphere.api.service.*;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.*;
import io.metersphere.base.mapper.ext.*;
@ -131,7 +128,8 @@ public class TestPlanService {
private ExtTestPlanLoadCaseMapper extTestPlanLoadCaseMapper;
@Resource
private ExtTestPlanScenarioCaseMapper extTestPlanScenarioCaseMapper;
@Resource
private ApiExecutionQueueService apiExecutionQueueService;
@Resource
private PerformanceTestService performanceTestService;
@Resource
@ -181,8 +179,6 @@ public class TestPlanService {
private ExtTestPlanExecutionQueueMapper extTestPlanExecutionQueueMapper;
public synchronized TestPlan addTestPlan(AddTestPlanRequest testPlan) {
if (getTestPlanByName(testPlan.getName()).size() > 0) {
MSException.throwException(Translator.get("plan_name_already_exists"));
@ -983,7 +979,7 @@ public class TestPlanService {
Map<String, String> scenarioReportMap = this.executeScenarioCase(planReportId, testPlanID, projectID, runModeConfig, triggerMode, userId, reportInfoDTO.getPlanScenarioIdMap());
//执行性能测试任务
LoggerUtil.info("开始执行测试计划性能用例 " + planReportId);
Map<String, String> loadCaseReportMap = this.executeLoadCaseTask(runModeConfig, triggerMode, reportInfoDTO.getPerformanceIdMap());
Map<String, String> loadCaseReportMap = this.executeLoadCaseTask(planReportId, runModeConfig, triggerMode, reportInfoDTO.getPerformanceIdMap());
LoggerUtil.info("开始生成测试计划报告 " + planReportId);
testPlanReportService.createTestPlanReportContentReportIds(planReportId, apiCaseReportMap, scenarioReportMap, loadCaseReportMap);
return planReportId;
@ -1044,7 +1040,7 @@ public class TestPlanService {
return returnMap;
}
private Map<String, String> executeLoadCaseTask(RunModeConfigDTO runModeConfig, String triggerMode, Map<String, String> performanceIdMap) {
private Map<String, String> executeLoadCaseTask(String planReportId, RunModeConfigDTO runModeConfig, String triggerMode, Map<String, String> performanceIdMap) {
Map<String, String> loadCaseReportMap = new HashMap<>();
for (Map.Entry<String, String> entry : performanceIdMap.entrySet()) {
String id = entry.getKey();
@ -1057,8 +1053,6 @@ public class TestPlanService {
}
if (StringUtils.equals(ReportTriggerMode.API.name(), triggerMode)) {
performanceRequest.setTriggerMode(ReportTriggerMode.TEST_PLAN_API.name());
} else if (StringUtils.equals(ReportTriggerMode.MANUAL.name(), triggerMode)) {
performanceRequest.setTriggerMode(ReportTriggerMode.MANUAL.name());
} else {
performanceRequest.setTriggerMode(ReportTriggerMode.TEST_PLAN_SCHEDULE.name());
}
@ -1084,6 +1078,11 @@ public class TestPlanService {
}
}
//将性能测试加入到队列中
apiExecutionQueueService.add(loadCaseReportMap, null, ApiRunMode.TEST_PLAN_PERFORMANCE_TEST.name(),
planReportId, null, null, new RunModeConfigDTO());
return loadCaseReportMap;
}

View File

@ -176,8 +176,8 @@ export default {
{value: 'UPDATE', label: this.$t('commons.update')},
{value: 'DELETE', label: this.$t('commons.delete')},
{value: 'COMPLETE', label: this.$t('commons.run_completed')},
// {value: 'SUCCESS_ONE_BY_ONE', label: ''},
// {value: 'FAIL_ONE_BY_ONE', label: ''},
{value: 'EXECUTE_SUCCESSFUL', label: this.$t('commons.run_success')},
{value: 'EXECUTE_FAILED', label: this.$t('commons.run_fail')},
],
variables: [
{