fix(接口自动化): 场景批量执行-并行 增加集合报告选项。

This commit is contained in:
fit2-zhao 2021-06-04 18:13:33 +08:00 committed by fit2-zhao
parent 3e4d9325ad
commit 50810099df
9 changed files with 174 additions and 107 deletions

View File

@ -1,5 +1,5 @@
package io.metersphere.api.dto.automation;
public enum ExecuteType {
Saved, Completed, Debug
Saved, Completed, Debug, Marge
}

View File

@ -68,6 +68,8 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
private ApiEnvironmentRunningParamService apiEnvironmentRunningParamService;
private TestPlanTestCaseService testPlanTestCaseService;
public String runMode = ApiRunMode.RUN.name();
// 测试ID
@ -136,6 +138,10 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
if(apiEnvironmentRunningParamService == null){
LogUtil.error("apiEnvironmentRunningParamService is required");
}
testPlanTestCaseService = CommonBeanFactory.getBean(TestPlanTestCaseService.class);
if(testPlanTestCaseService == null){
LogUtil.error("testPlanTestCaseService is required");
}
super.setupTest(context);
}
@ -155,11 +161,6 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
// 一个脚本里可能包含多个场景(ThreadGroup)所以要区分开key: 场景Id
final Map<String, ScenarioResult> scenarios = new LinkedHashMap<>();
queue.forEach(result -> {
// if(result instanceof SampleResult){
// if(testResult.getTotal()>0){
// testResult.setTotal(testResult.getTotal()-1);
// }
// }
// 线程名称: <场景名> <场景Index>-<请求Index>, 例如Scenario 2-1
if(StringUtils.equals(result.getSampleLabel(), RunningParamKeys.RUNNING_DEBUG_SAMPLER_NAME)){
String evnStr = result.getResponseDataAsString();
@ -299,16 +300,16 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
ApiScenarioReport scenarioReport = apiScenarioReportService.complete(testResult, this.runMode);
//环境
ApiScenarioWithBLOBs apiScenario = apiAutomationService.getDto(scenarioReport.getScenarioId());
String executionEnvironment = apiScenario.getScenarioDefinition();
JSONObject json = JSONObject.parseObject(executionEnvironment);
String name = "";
if (json != null && json.getString("environmentMap") != null && json.getString("environmentMap").length() > 2) {
JSONObject environment = JSONObject.parseObject(json.getString("environmentMap"));
String environmentId = environment.get(apiScenario.getProjectId()).toString();
name = apiAutomationService.get(environmentId).getName();
if(apiScenario!= null ) {
String executionEnvironment = apiScenario.getScenarioDefinition();
JSONObject json = JSONObject.parseObject(executionEnvironment);
if (json != null && json.getString("environmentMap") != null && json.getString("environmentMap").length() > 2) {
JSONObject environment = JSONObject.parseObject(json.getString("environmentMap"));
String environmentId = environment.get(apiScenario.getProjectId()).toString();
name = apiAutomationService.get(environmentId).getName();
}
}
//时间
Long time = scenarioReport.getUpdateTime();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@ -341,7 +342,6 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
queue.clear();
super.teardownTest(context);
TestPlanTestCaseService testPlanTestCaseService = CommonBeanFactory.getBean(TestPlanTestCaseService.class);
List<String> ids = testPlanTestCaseService.getTestPlanTestCaseIds(testResult.getTestId());
if (ids.size() > 0) {
try {

View File

@ -0,0 +1,25 @@
package io.metersphere.api.jmeter;
import io.metersphere.api.service.ApiScenarioReportService;
import io.metersphere.commons.utils.CommonBeanFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class FixedTask {
@Scheduled(cron = "*/3 * * * * ?")
public void execute() {
ApiScenarioReportService scenarioReportService = CommonBeanFactory.getBean(ApiScenarioReportService.class);
if (MessageCache.cache != null && MessageCache.cache.size() > 0) {
for (String key : MessageCache.cache.keySet()) {
ReportCounter counter = MessageCache.cache.get(key);
// 合并
if (counter.getNumber() == counter.getReportIds().size()) {
scenarioReportService.margeReport(key, counter.getReportIds());
MessageCache.cache.remove(key);
}
}
}
}
}

View File

@ -0,0 +1,8 @@
package io.metersphere.api.jmeter;
import java.util.HashMap;
import java.util.Map;
public class MessageCache {
public static Map<String, ReportCounter> cache = new HashMap<>();
}

View File

@ -0,0 +1,11 @@
package io.metersphere.api.jmeter;
import lombok.Data;
import java.util.List;
@Data
public class ReportCounter {
private int number;
private List<String> reportIds;
}

View File

@ -20,6 +20,8 @@ import io.metersphere.api.dto.definition.request.unknown.MsJmeterElement;
import io.metersphere.api.dto.definition.request.variable.ScenarioVariable;
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
import io.metersphere.api.jmeter.JMeterService;
import io.metersphere.api.jmeter.MessageCache;
import io.metersphere.api.jmeter.ReportCounter;
import io.metersphere.api.parse.ApiImportParser;
import io.metersphere.api.service.task.ParallelScenarioExecTask;
import io.metersphere.api.service.task.SerialScenarioExecTask;
@ -102,6 +104,8 @@ public class ApiAutomationService {
@Resource
private ApiScenarioReportMapper apiScenarioReportMapper;
@Resource
private ApiScenarioReportDetailMapper apiScenarioReportDetailMapper;
@Resource
@Lazy
private TestPlanScenarioCaseService testPlanScenarioCaseService;
@Resource
@ -710,7 +714,7 @@ public class ApiAutomationService {
return null;
}
public APIScenarioReportResult createScenarioReport(String id, String scenarioId, String scenarioName, String triggerMode, String execType, String projectId, String userID, RunModeConfig config) {
public APIScenarioReportResult createScenarioReport(String id, String scenarioId, String scenarioName, String triggerMode, String execType, String projectId, String userID) {
APIScenarioReportResult report = new APIScenarioReportResult();
if (triggerMode.equals(ApiRunMode.SCENARIO.name()) || triggerMode.equals(ApiRunMode.DEFINITION.name())) {
triggerMode = ReportTriggerMode.MANUAL.name();
@ -724,10 +728,7 @@ public class ApiAutomationService {
}
report.setUpdateTime(System.currentTimeMillis());
report.setCreateTime(System.currentTimeMillis());
if (config != null && config.getMode().equals(RunModeConstants.SERIAL.toString())) {
report.setCreateTime(System.currentTimeMillis() + 2000);
report.setUpdateTime(System.currentTimeMillis() + 2000);
}
report.setStatus(APITestStatus.Running.name());
if (StringUtils.isNotEmpty(userID)) {
report.setUserId(userID);
@ -870,8 +871,8 @@ public class ApiAutomationService {
(query) -> extApiScenarioMapper.selectIdsByQuery((ApiScenarioRequest) query));
List<String> ids = request.getIds();
//检查是否有正在执行中的情景
// this.checkScenarioIsRunning(ids);
// 生成集成报告
String serialReportId = null;
StringBuilder idStr = new StringBuilder();
ids.forEach(item -> {
@ -884,11 +885,14 @@ public class ApiAutomationService {
}
// 环境检查
this.checkEnv(request, apiScenarios);
if (request.getConfig() != null && request.getConfig().getMode().equals(RunModeConstants.SERIAL.toString())) {
if (StringUtils.equals(request.getConfig().getReportType(), RunModeConstants.SET_REPORT.toString()) && StringUtils.isNotEmpty(request.getConfig().getReportName())) {
// 集合报告设置
if (request.getConfig() != null && StringUtils.equals(request.getConfig().getReportType(), RunModeConstants.SET_REPORT.toString()) && StringUtils.isNotEmpty(request.getConfig().getReportName())) {
if (request.getConfig().getMode().equals(RunModeConstants.SERIAL.toString())) {
request.setExecuteType(ExecuteType.Completed.name());
} else {
request.setExecuteType(ExecuteType.Marge.name());
}
serialReportId = UUID.randomUUID().toString();
}
if (StringUtils.isEmpty(request.getTriggerMode())) {
request.setTriggerMode(ReportTriggerMode.MANUAL.name());
@ -919,40 +923,45 @@ public class ApiAutomationService {
if (request.isTestPlanScheduleJob()) {
String savedScenarioId = testPlanScenarioId + ":" + request.getTestPlanReportId();
report = createScenarioReport(reportId, savedScenarioId, item.getName(), request.getTriggerMode(),
request.getExecuteType(), item.getProjectId(), request.getReportUserID(), null);
request.getExecuteType(), item.getProjectId(), request.getReportUserID());
} else {
report = createScenarioReport(reportId, testPlanScenarioId, item.getName(), request.getTriggerMode(),
request.getExecuteType(), item.getProjectId(), request.getReportUserID(), null);
request.getExecuteType(), item.getProjectId(), request.getReportUserID());
}
} else {
report = createScenarioReport(reportId, item.getId(), item.getName(), request.getTriggerMode(),
request.getExecuteType(), item.getProjectId(), request.getReportUserID(), null);
report = createScenarioReport(reportId, ExecuteType.Marge.name().equals(request.getExecuteType()) ? serialReportId : item.getId(), item.getName(), request.getTriggerMode(),
request.getExecuteType(), item.getProjectId(), request.getReportUserID());
}
// 生成报告和HashTree
HashTree hashTree = null;
try {
hashTree = generateHashTree(item, reportId, planEnvMap);
// 生成报告和HashTree
HashTree hashTree = generateHashTree(item, reportId, planEnvMap);
map.put(report, hashTree);
scenarioIds.add(item.getId());
scenarioNames.append(item.getName()).append(",");
// 重置报告ID
reportId = UUID.randomUUID().toString();
} catch (Exception ex) {
MSException.throwException("解析运行步骤失败!场景名称:" + item.getName());
}
map.put(report, hashTree);
scenarioIds.add(item.getId());
scenarioNames.append(item.getName()).append(",");
// 重置报告ID
reportId = UUID.randomUUID().toString();
}
// 生成集成报告
String serialReportId = null;
if (request.getConfig() != null && request.getConfig().getMode().equals(RunModeConstants.SERIAL.toString()) && StringUtils.equals(request.getConfig().getReportType(), RunModeConstants.SET_REPORT.toString()) && StringUtils.isNotEmpty(request.getConfig().getReportName())) {
if (request.getConfig() != null && StringUtils.equals(request.getConfig().getReportType(), RunModeConstants.SET_REPORT.toString()) && StringUtils.isNotEmpty(request.getConfig().getReportName())) {
request.getConfig().setReportId(UUID.randomUUID().toString());
APIScenarioReportResult report = createScenarioReport(request.getConfig().getReportId(), JSON.toJSONString(scenarioIds), scenarioNames.deleteCharAt(scenarioNames.toString().length() - 1).toString(), ReportTriggerMode.MANUAL.name(),
ExecuteType.Saved.name(), request.getProjectId(), request.getReportUserID(), request.getConfig());
ExecuteType.Saved.name(), request.getProjectId(), request.getReportUserID());
report.setName(request.getConfig().getReportName());
report.setId(serialReportId);
apiScenarioReportMapper.insert(report);
serialReportId = report.getId();
// 增加并行集合报告
if (request.getConfig() != null && request.getConfig().getMode().equals(RunModeConstants.PARALLEL.toString())) {
List<String> reportIds = map.keySet().stream()
.collect(Collectors.toList()).stream()
.map(ApiScenarioReport::getId).collect(Collectors.toList());
ReportCounter counter = new ReportCounter();
counter.setNumber(0);
counter.setReportIds(reportIds);
MessageCache.cache.put(serialReportId, counter);
}
}
// 开始执行
this.run(map, request, serialReportId);
@ -1067,7 +1076,6 @@ public class ApiAutomationService {
if (reportIds != null) {
//如果是测试计划页面触发的执行方式生成报告时createScenarioReport第二个参数需要特殊处理
APIScenarioReportResult report = null;
// if (StringUtils.equals(request.getRunMode(), ApiRunMode.SCENARIO_PLAN.name())) {
if (StringUtils.equalsAny(request.getRunMode(), ApiRunMode.SCENARIO_PLAN.name(), ApiRunMode.SCHEDULE_SCENARIO_PLAN.name())) {
String testPlanScenarioId = item.getId();
if (request.getScenarioTestPlanIdMap() != null && request.getScenarioTestPlanIdMap().containsKey(item.getId())) {
@ -1082,14 +1090,14 @@ public class ApiAutomationService {
if (request.isTestPlanScheduleJob()) {
String savedScenarioId = testPlanScenarioId + ":" + request.getTestPlanReportId();
report = createScenarioReport(group.getName(), savedScenarioId, item.getName(), request.getTriggerMode(),
request.getExecuteType(), item.getProjectId(), request.getReportUserID(), null);
request.getExecuteType(), item.getProjectId(), request.getReportUserID());
} else {
report = createScenarioReport(group.getName(), testPlanScenarioId, item.getName(), request.getTriggerMode() == null ? ReportTriggerMode.MANUAL.name() : request.getTriggerMode(),
request.getExecuteType(), item.getProjectId(), request.getReportUserID(), request.getConfig());
request.getExecuteType(), item.getProjectId(), request.getReportUserID());
}
} else {
report = createScenarioReport(group.getName(), item.getId(), item.getName(), request.getTriggerMode() == null ? ReportTriggerMode.MANUAL.name() : request.getTriggerMode(),
request.getExecuteType(), item.getProjectId(), request.getReportUserID(), request.getConfig());
request.getExecuteType(), item.getProjectId(), request.getReportUserID());
}
batchMapper.insert(report);
reportIds.add(group.getName());
@ -1108,9 +1116,9 @@ public class ApiAutomationService {
}
private void preduceMsScenario(MsScenario scenario) {
if(scenario.getHashTree()!=null){
if (scenario.getHashTree() != null) {
for (MsTestElement itemElement : scenario.getHashTree()) {
if(itemElement instanceof MsScenario){
if (itemElement instanceof MsScenario) {
itemElement.setId(UUID.randomUUID().toString());
}
}
@ -1294,9 +1302,9 @@ public class ApiAutomationService {
envConfig.put(id, env);
});
}
try{
try {
this.preduceTestElement(request);
}catch (Exception e){
} catch (Exception e) {
}
ParameterConfig config = new ParameterConfig();
config.setConfig(envConfig);
@ -1309,7 +1317,7 @@ public class ApiAutomationService {
}
APIScenarioReportResult report = createScenarioReport(request.getId(), request.getScenarioId(), request.getScenarioName(), ReportTriggerMode.MANUAL.name(), request.getExecuteType(), request.getProjectId(),
SessionUtils.getUserId(), null);
SessionUtils.getUserId());
apiScenarioReportMapper.insert(report);
uploadBodyFiles(request.getBodyFileRequestIds(), bodyFiles);
@ -1320,14 +1328,14 @@ public class ApiAutomationService {
return request.getId();
}
private void preduceTestElement(RunDefinitionRequest request) throws Exception{
if(request.getTestElement() != null){
private void preduceTestElement(RunDefinitionRequest request) throws Exception {
if (request.getTestElement() != null) {
for (MsTestElement threadGroup : request.getTestElement().getHashTree()) {
if(threadGroup instanceof MsThreadGroup && threadGroup.getHashTree() != null){
for (MsTestElement scenario: threadGroup.getHashTree()) {
if(scenario instanceof MsScenario && scenario.getHashTree() != null){
if (threadGroup instanceof MsThreadGroup && threadGroup.getHashTree() != null) {
for (MsTestElement scenario : threadGroup.getHashTree()) {
if (scenario instanceof MsScenario && scenario.getHashTree() != null) {
for (MsTestElement itemElement : scenario.getHashTree()) {
if(itemElement instanceof MsScenario){
if (itemElement instanceof MsScenario) {
itemElement.setId(UUID.randomUUID().toString());
}
}

View File

@ -11,6 +11,8 @@ import io.metersphere.api.dto.automation.APIScenarioReportResult;
import io.metersphere.api.dto.automation.ExecuteType;
import io.metersphere.api.dto.automation.ScenarioStatus;
import io.metersphere.api.dto.datacount.ApiDataCountResult;
import io.metersphere.api.jmeter.MessageCache;
import io.metersphere.api.jmeter.ReportCounter;
import io.metersphere.api.jmeter.ScenarioResult;
import io.metersphere.api.jmeter.TestResult;
import io.metersphere.base.domain.*;
@ -68,7 +70,7 @@ public class ApiScenarioReportService {
return updateSchedulePlanCase(result, runMode);
} else {
updateScenarioStatus(result.getTestId());
return updateScenario(result, runMode);
return updateScenario(result);
}
}
return null;
@ -430,13 +432,10 @@ public class ApiScenarioReportService {
}
}
public ApiScenarioReport updateScenario(TestResult result, String runMode) {
public ApiScenarioReport updateScenario(TestResult result) {
ApiScenarioReport lastReport = null;
StringBuilder scenarioIds = new StringBuilder();
StringBuilder scenarioNames = new StringBuilder();
String projectId = null;
String userId = null;
TestResult fullResult = createTestResult(result);
List<String> reportIds = new LinkedList<>();
for (ScenarioResult item : result.getScenarios()) {
@ -446,38 +445,45 @@ public class ApiScenarioReportService {
startTime = item.getRequestResults().get(0).getStartTime();
}
ApiScenarioReport report = editReport(item, startTime);
TestResult newResult = createTestResult(result.getTestId(), item);
item.setName(report.getScenarioName());
newResult.addScenario(item);
fullResult.addScenario(item);
projectId = report.getProjectId();
userId = report.getUserId();
scenarioIds.append(item.getName()).append(",");
scenarioNames.append(report.getName()).append(",");
// 报告详情内容
ApiScenarioReportDetail detail = new ApiScenarioReportDetail();
detail.setContent(JSON.toJSONString(newResult).getBytes(StandardCharsets.UTF_8));
detail.setReportId(report.getId());
detail.setProjectId(report.getProjectId());
apiScenarioReportDetailMapper.insert(detail);
// 更新场景状态
ApiScenario scenario = apiScenarioMapper.selectByPrimaryKey(report.getScenarioId());
if (scenario != null) {
if (item.getError() > 0) {
scenario.setLastResult("Fail");
} else {
scenario.setLastResult("Success");
if (report != null) {
// 合并并行报告
TestResult newResult = createTestResult(result.getTestId(), item);
item.setName(report.getScenarioName());
newResult.addScenario(item);
fullResult.addScenario(item);
scenarioIds.append(item.getName()).append(",");
scenarioNames.append(report.getName()).append(",");
// 报告详情内容
ApiScenarioReportDetail detail = new ApiScenarioReportDetail();
detail.setContent(JSON.toJSONString(newResult).getBytes(StandardCharsets.UTF_8));
detail.setReportId(report.getId());
detail.setProjectId(report.getProjectId());
apiScenarioReportDetailMapper.insert(detail);
// 更新场景状态
ApiScenario scenario = apiScenarioMapper.selectByPrimaryKey(report.getScenarioId());
if (scenario != null) {
if (item.getError() > 0) {
scenario.setLastResult("Fail");
} else {
scenario.setLastResult("Success");
}
String passRate = new DecimalFormat("0%").format((float) item.getSuccess() / (item.getSuccess() + item.getError()));
scenario.setPassRate(passRate);
scenario.setReportId(report.getId());
apiScenarioMapper.updateByPrimaryKey(scenario);
}
String passRate = new DecimalFormat("0%").format((float) item.getSuccess() / (item.getSuccess() + item.getError()));
scenario.setPassRate(passRate);
scenario.setReportId(report.getId());
apiScenarioMapper.updateByPrimaryKey(scenario);
lastReport = report;
}
lastReport = report;
reportIds.add(report.getId());
if (report.getExecuteType().equals(ExecuteType.Marge.name())) {
Object obj = MessageCache.cache.get(report.getScenarioId());
if (obj != null) {
ReportCounter counter = (ReportCounter) obj;
counter.setNumber(counter.getNumber() + 1);
MessageCache.cache.put(report.getScenarioId(), counter);
}
}
}
// 合并生成一份报告
// margeReport(result, scenarioIds, scenarioNames, runMode, projectId, userId, reportIds);
return lastReport;
}

View File

@ -990,7 +990,7 @@ public class TestPlanService {
APIScenarioReportResult report = apiAutomationService.createScenarioReport(group.getName(),
planScenarioID + ":" + request.getTestPlanReportId(),
item.getName(), request.getTriggerMode() == null ? ReportTriggerMode.MANUAL.name() : request.getTriggerMode(),
request.getExecuteType(), item.getProjectId(), request.getReportUserID(), null);
request.getExecuteType(), item.getProjectId(), request.getReportUserID());
apiScenarioReportMapper.insert(report);
group.setHashTree(scenarios);
testPlan.getHashTree().add(group);

View File

@ -1,9 +1,9 @@
<template>
<el-dialog
destroy-on-close
:title="$t('load_test.runtime_config')"
width="450px"
:visible.sync="runModeVisible"
destroy-on-close
:title="$t('load_test.runtime_config')"
width="450px"
:visible.sync="runModeVisible"
>
<div>
<span class="ms-mode-span">{{ $t("run_mode.title") }}</span>
@ -30,10 +30,10 @@
</el-checkbox>
<el-select :disabled="!runConfig.runWithinResourcePool" v-model="runConfig.resourcePoolId" size="mini">
<el-option
v-for="item in resourcePools"
:key="item.id"
:label="item.name"
:value="item.id">
v-for="item in resourcePools"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
</div>
@ -46,6 +46,13 @@
<span class="ms-mode-span">{{ $t("run_mode.other_config") }}</span>
</el-col>
<el-col :span="18">
<div>
<el-radio-group v-model="runConfig.reportType">
<el-radio label="iddReport">{{ $t("run_mode.idd_report") }}</el-radio>
<el-radio label="setReport">{{ $t("run_mode.set_report") }}</el-radio>
</el-radio-group>
</div>
<div style="padding-top: 10px">
<el-checkbox v-model="runConfig.runWithinResourcePool" style="padding-right: 10px;">
{{ $t('run_mode.run_with_resource_pool') }}
</el-checkbox>
@ -57,18 +64,18 @@
:value="item.id">
</el-option>
</el-select>
</div>
</el-col>
</el-row>
</div>
<div class="ms-mode-div" v-if="runConfig.reportType === 'setReport' && runConfig.mode==='serial'">
<div class="ms-mode-div" v-if="runConfig.reportType === 'setReport'">
<span class="ms-mode-span">{{ $t("run_mode.report_name") }}</span>
<el-input
v-model="runConfig.reportName"
:placeholder="$t('commons.input_content')"
size="small"
style="width: 200px"
/>
v-model="runConfig.reportName"
:placeholder="$t('commons.input_content')"
size="small"
style="width: 300px"/>
</div>
<template v-slot:footer>
<ms-dialog-footer @cancel="close" @confirm="handleRunBatch"/>
@ -104,6 +111,8 @@ export default {
changeMode() {
this.runConfig.runWithinResourcePool = false;
this.runConfig.resourcePoolId = null;
this.runConfig.reportType = "iddReport";
this.runConfig.reportName = "";
},
close() {
this.runConfig = {