feat(测试跟踪): 测试计划-性能测试用例添加串行并行执行模式

This commit is contained in:
fit2-zhao 2021-04-20 16:40:51 +08:00 committed by fit2-zhao
parent 1bf00efb41
commit b6b649b679
11 changed files with 288 additions and 37 deletions

View File

@ -6,7 +6,6 @@ import lombok.Data;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import java.beans.Beans;
import java.util.List;
import java.util.stream.Collectors;
@ -17,6 +16,7 @@ public class HttpConfig {
private String protocol = "https";
private String defaultCondition;
private int port;
private boolean isMock;
private List<HttpConfigCondition> conditions;
private List<KeyValue> headers;

View File

@ -798,7 +798,7 @@ public class ApiAutomationService {
group.setOnSampleError(request.getConfig().isOnSampleError());
}
// 批量执行的结果直接存储为报告
if (isFirst) {
if (isFirst && StringUtils.isNotEmpty(request.getId())) {
group.setName(request.getId());
}
ObjectMapper mapper = new ObjectMapper();
@ -848,11 +848,11 @@ public class ApiAutomationService {
testPlan.getHashTree().add(group);
isFirst = false;
}
testPlan.toHashTree(jmeterHashTree, testPlan.getHashTree(), new ParameterConfig());
} catch (Exception ex) {
MSException.throwException(ex.getMessage());
}
testPlan.toHashTree(jmeterHashTree, testPlan.getHashTree(), new ParameterConfig());
return jmeterHashTree;
}

View File

@ -11,6 +11,7 @@ import io.metersphere.track.dto.TestPlanLoadCaseDTO;
import io.metersphere.track.request.testplan.LoadCaseReportBatchRequest;
import io.metersphere.track.request.testplan.LoadCaseReportRequest;
import io.metersphere.track.request.testplan.LoadCaseRequest;
import io.metersphere.track.request.testplan.RunBatchTestPlanRequest;
import io.metersphere.track.service.TestPlanLoadCaseService;
import org.springframework.web.bind.annotation.*;
@ -51,6 +52,11 @@ public class TestPlanLoadCaseController {
return testPlanLoadCaseService.run(request);
}
@PostMapping("/run/batch")
public void runBatch(@RequestBody RunBatchTestPlanRequest request) {
testPlanLoadCaseService.runBatch(request);
}
@PostMapping("/report/exist")
public Boolean isExistReport(@RequestBody LoadCaseReportRequest request) {
return testPlanLoadCaseService.isExistReport(request);

View File

@ -0,0 +1,16 @@
package io.metersphere.track.request.testplan;
import io.metersphere.api.dto.automation.RunModeConfig;
import io.metersphere.performance.request.RunTestPlanRequest;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
public class RunBatchTestPlanRequest {
private List<RunTestPlanRequest> requests;
private RunModeConfig config;
private String userId;
}

View File

@ -7,6 +7,7 @@ import io.metersphere.base.mapper.TestPlanLoadCaseMapper;
import io.metersphere.base.mapper.TestPlanMapper;
import io.metersphere.base.mapper.ext.ExtTestPlanLoadCaseMapper;
import io.metersphere.commons.constants.TestPlanStatus;
import io.metersphere.commons.exception.MSException;
import io.metersphere.controller.request.OrderRequest;
import io.metersphere.performance.request.RunTestPlanRequest;
import io.metersphere.performance.service.PerformanceTestService;
@ -14,6 +15,9 @@ import io.metersphere.track.dto.TestPlanLoadCaseDTO;
import io.metersphere.track.request.testplan.LoadCaseReportBatchRequest;
import io.metersphere.track.request.testplan.LoadCaseReportRequest;
import io.metersphere.track.request.testplan.LoadCaseRequest;
import io.metersphere.track.request.testplan.RunBatchTestPlanRequest;
import io.metersphere.track.service.utils.ParallelExecTask;
import io.metersphere.track.service.utils.SerialExecTask;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
@ -26,6 +30,9 @@ import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
@Service
@ -121,6 +128,38 @@ public class TestPlanLoadCaseService {
return reportId;
}
public void runBatch(RunBatchTestPlanRequest request) {
if (request.getConfig() != null && request.getConfig().getMode().equals("serial")) {
try {
serialRun(request);
} catch (Exception e) {
MSException.throwException(e.getMessage());
}
} else {
ExecutorService executorService = Executors.newFixedThreadPool(request.getRequests().size());
request.getRequests().forEach(item -> {
executorService.submit(new ParallelExecTask(performanceTestService, testPlanLoadCaseMapper, item));
});
}
}
private void serialRun(RunBatchTestPlanRequest request) throws Exception {
ExecutorService executorService = Executors.newFixedThreadPool(request.getRequests().size());
for (RunTestPlanRequest runTestPlanRequest : request.getRequests()) {
Future<LoadTestReportWithBLOBs> future = executorService.submit(new SerialExecTask(performanceTestService, testPlanLoadCaseMapper, loadTestReportMapper, runTestPlanRequest, request.getConfig()));
LoadTestReportWithBLOBs report = future.get();
// 如果开启失败结束执行则判断返回结果状态
if (request.getConfig().isOnSampleError()) {
TestPlanLoadCaseExample example = new TestPlanLoadCaseExample();
example.createCriteria().andLoadReportIdEqualTo(report.getId());
List<TestPlanLoadCase> cases = testPlanLoadCaseMapper.selectByExample(example);
if (CollectionUtils.isEmpty(cases) || !cases.get(0).getStatus().equals("success")) {
break;
}
}
}
}
public Boolean isExistReport(LoadCaseReportRequest request) {
String reportId = request.getReportId();
String testPlanLoadCaseId = request.getTestPlanLoadCaseId();
@ -159,11 +198,11 @@ public class TestPlanLoadCaseService {
testPlanLoadCaseMapper.deleteByExample(example);
}
public void batchDelete(LoadCaseReportBatchRequest request){
public void batchDelete(LoadCaseReportBatchRequest request) {
List<String> ids = request.getIds();
if(request.getCondition()!=null && request.getCondition().isSelectAll()){
if (request.getCondition() != null && request.getCondition().isSelectAll()) {
ids = this.selectTestPlanLoadCaseIds(request.getCondition());
if(request.getCondition().getUnSelectIds()!=null){
if (request.getCondition().getUnSelectIds() != null) {
ids.removeAll(request.getCondition().getUnSelectIds());
}
}

View File

@ -111,8 +111,10 @@ public class TestPlanScenarioCaseService {
RunScenarioRequest request = new RunScenarioRequest();
request.setIds(scenarioIds);
request.setReportId(testPlanScenarioRequest.getId());
request.setScenarioTestPlanIdMap(scenarioIdApiScarionMap);
request.setRunMode(ApiRunMode.SCENARIO_PLAN.name());
request.setId(testPlanScenarioRequest.getId());
return apiAutomationService.run(request);
}

View File

@ -0,0 +1,42 @@
/**
*
*/
package io.metersphere.track.service.utils;
import io.metersphere.base.domain.TestPlanLoadCase;
import io.metersphere.base.mapper.TestPlanLoadCaseMapper;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.performance.request.RunTestPlanRequest;
import io.metersphere.performance.service.PerformanceTestService;
import java.util.concurrent.Callable;
public class ParallelExecTask<T> implements Callable<T> {
private RunTestPlanRequest request;
private PerformanceTestService performanceTestService;
private TestPlanLoadCaseMapper testPlanLoadCaseMapper;
public ParallelExecTask(PerformanceTestService performanceTestService, TestPlanLoadCaseMapper testPlanLoadCaseMapper, RunTestPlanRequest request) {
this.performanceTestService = performanceTestService;
this.testPlanLoadCaseMapper = testPlanLoadCaseMapper;
this.request = request;
}
@Override
public T call() {
try {
String reportId = performanceTestService.run(request);
TestPlanLoadCase testPlanLoadCase = new TestPlanLoadCase();
testPlanLoadCase.setId(request.getTestPlanLoadId());
testPlanLoadCase.setLoadReportId(reportId);
testPlanLoadCaseMapper.updateByPrimaryKeySelective(testPlanLoadCase);
return (T) reportId;
} catch (Exception ex) {
LogUtil.error(ex.getMessage());
MSException.throwException(ex.getMessage());
return null;
}
}
}

View File

@ -0,0 +1,61 @@
/**
*
*/
package io.metersphere.track.service.utils;
import io.metersphere.api.dto.automation.RunModeConfig;
import io.metersphere.base.domain.LoadTestReportWithBLOBs;
import io.metersphere.base.domain.TestPlanLoadCase;
import io.metersphere.base.mapper.LoadTestReportMapper;
import io.metersphere.base.mapper.TestPlanLoadCaseMapper;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.performance.request.RunTestPlanRequest;
import io.metersphere.performance.service.PerformanceTestService;
import java.util.concurrent.Callable;
public class SerialExecTask<T> implements Callable<T> {
private RunTestPlanRequest request;
private RunModeConfig config;
private PerformanceTestService performanceTestService;
private TestPlanLoadCaseMapper testPlanLoadCaseMapper;
private LoadTestReportMapper loadTestReportMapper;
public SerialExecTask(PerformanceTestService performanceTestService, TestPlanLoadCaseMapper testPlanLoadCaseMapper,LoadTestReportMapper loadTestReportMapper, RunTestPlanRequest request, RunModeConfig config) {
this.performanceTestService = performanceTestService;
this.testPlanLoadCaseMapper = testPlanLoadCaseMapper;
this.loadTestReportMapper = loadTestReportMapper;
this.request = request;
this.config = config;
}
@Override
public T call() {
try {
// 串行开启轮询等待
String reportId = performanceTestService.run(request);
TestPlanLoadCase testPlanLoadCase = new TestPlanLoadCase();
testPlanLoadCase.setId(request.getTestPlanLoadId());
testPlanLoadCase.setLoadReportId(reportId);
testPlanLoadCaseMapper.updateByPrimaryKeySelective(testPlanLoadCase);
LoadTestReportWithBLOBs report = null;
// 轮询查看报告状态最多200次防止死循环
int index = 1;
while (index < 200) {
Thread.sleep(3000);
index++;
report = loadTestReportMapper.selectByPrimaryKey(reportId);
if (report != null && (report.getStatus().equals("Completed") || report.getStatus().equals("Error") || report.getStatus().equals("Saved"))) {
break;
}
}
return (T) report;
} catch (Exception ex) {
LogUtil.error(ex.getMessage());
MSException.throwException(ex.getMessage());
return null;
}
}
}

View File

@ -463,7 +463,6 @@ export default {
this.$success(this.$t('test_track.cancel_relevance_success'));
});
}
}
}
});
@ -482,9 +481,7 @@ export default {
},
singleRun(row) {
this.runData = [];
this.rowLoading = row.id;
this.$get('/api/testcase/get/' + row.caseId, (response) => {
let apiCase = response.data;
let request = JSON.parse(apiCase.request);
@ -504,6 +501,8 @@ export default {
return new Promise((resolve) => {
let index = 1;
this.runData = [];
//
this.orderBySelectRows(this.selectRows);
this.selectRows.forEach(row => {
this.$get('/api/testcase/get/' + row.caseId, (response) => {
let apiCase = response.data;
@ -512,7 +511,7 @@ export default {
request.id = row.id;
request.useEnvironment = row.environmentId;
this.runData.unshift(request);
if (this.selectRows.size === index) {
if (this.selectRows.length === index) {
resolve();
}
index++;
@ -558,12 +557,37 @@ export default {
//
}
},
handleBatchExecute() {
this.getData().then(() => {
if (this.runData && this.runData.length > 0) {
this.$refs.runMode.open();
orderBySelectRows(rows){
let selectIds = Array.from(rows).map(row => row.id);
let array = [];
for(let i in this.tableData){
if(selectIds.indexOf(this.tableData[i].id)!==-1){
array.push(this.tableData[i]);
}
});
}
this.selectRows = array;
},
handleBatchExecute() {
if(this.condition != null && this.condition.selectAll) {
this.$alert(this.$t('commons.option_cannot_spread_pages'), '', {
confirmButtonText: this.$t('commons.confirm'),
callback: (action) => {
if (action === 'confirm') {
this.getData().then(() => {
if (this.runData && this.runData.length > 0) {
this.$refs.runMode.open();
}
});
}
}
})
}else {
this.getData().then(() => {
if (this.runData && this.runData.length > 0) {
this.$refs.runMode.open();
}
});
}
},
handleRunBatch(config) {
let testPlan = new TestPlan();

View File

@ -294,9 +294,31 @@ export default {
})
},
handleBatchExecute() {
this.$refs.runMode.open();
if(this.condition != null && this.condition.selectAll) {
this.$alert(this.$t('commons.option_cannot_spread_pages'), '', {
confirmButtonText: this.$t('commons.confirm'),
callback: (action) => {
if (action === 'confirm') {
this.$refs.runMode.open();
}
}
})
}else {
this.$refs.runMode.open();
}
},
orderBySelectRows(rows){
let selectIds = Array.from(rows).map(row => row.id);
let array = [];
for(let i in this.tableData){
if(selectIds.indexOf(this.tableData[i].id)!==-1){
array.push(this.tableData[i]);
}
}
this.selectRows = array;
},
handleRunBatch(config){
this.orderBySelectRows(this.selectRows);
if (this.reviewId) {
let param = {config : config,planCaseIds:[]};
this.selectRows.forEach(row => {
@ -320,6 +342,7 @@ export default {
execute(row) {
this.infoDb = false;
let param ={planCaseIds: []};
this.reportId = "";
this.buildExecuteParam(param,row);
if (this.planId) {
this.$post("/test/plan/scenario/case/run", param, response => {

View File

@ -130,6 +130,7 @@
<ms-table-pagination :change="initTable" :current-page.sync="currentPage" :page-size.sync="pageSize"
:total="total"/>
</el-card>
<ms-plan-run-mode @handleRunBatch="runBatch" ref="runMode"/>
<load-case-report :report-id="reportId" ref="loadCaseReport" @refresh="initTable"/>
</div>
@ -162,6 +163,7 @@ import {Test_Plan_Load_Case, Track_Test_Case} from "@/business/components/common
import {getCurrentUser} from "@/common/js/utils";
import HeaderLabelOperate from "@/business/components/common/head/HeaderLabelOperate";
import MsTableHeaderSelectPopover from "@/business/components/common/components/table/MsTableHeaderSelectPopover";
import MsPlanRunMode from "../../../common/PlanRunMode";
export default {
name: "TestPlanLoadCaseList",
@ -174,7 +176,8 @@ export default {
MsTablePagination,
MsPerformanceTestStatus,
MsTableOperatorButton,
MsTableHeaderSelectPopover
MsTableHeaderSelectPopover,
MsPlanRunMode
},
data() {
return {
@ -225,8 +228,6 @@ export default {
created() {
this.initTable();
this.refreshStatus();
},
watch: {
selectProjectId() {
@ -237,6 +238,60 @@ export default {
}
},
methods: {
orderBySelectRows(rows){
let selectIds = Array.from(rows).map(row => row.id);
let array = [];
for(let i in this.tableData){
if(selectIds.indexOf(this.tableData[i].id)!==-1){
array.push(this.tableData[i]);
}
}
this.selectRows = array;
},
runBatch(config){
this.orderBySelectRows(this.selectRows);
if(this.condition != null && this.condition.selectAll){
this.$alert(this.$t('commons.option_cannot_spread_pages'), '', {
confirmButtonText: this.$t('commons.confirm'),
callback: (action) => {
if (action === 'confirm') {
let runArr = [];
this.selectRows.forEach(loadCase => {
runArr.push( {
id: loadCase.loadCaseId,
testPlanLoadId: loadCase.id,
triggerMode: 'CASE'
})
});
let obj = {config:config,requests:runArr,userId:getCurrentUser().id};
this._runBatch(obj);
this.refreshStatus();
}
}
});
}else {
let runArr = [];
this.selectRows.forEach(loadCase => {
runArr.push( {
id: loadCase.loadCaseId,
testPlanLoadId: loadCase.id,
triggerMode: 'CASE'
})
});
let obj = {config:config,requests:runArr,userId:getCurrentUser().id};
this._runBatch(obj);
this.initTable();
this.refreshStatus();
}
},
_runBatch(loadCases) {
this.$post('/test/plan/load/case/run/batch',loadCases, response => {
});
this.$success(this.$t('test_track.plan.load_case.exec'));
this.initTable();
this.refreshStatus();
},
customHeader() {
this.$refs.headerCustom.open(this.tableLabel)
},
@ -338,24 +393,7 @@ export default {
})
},
handleRunBatch() {
if(this.condition != null && this.condition.selectAll){
this.$alert(this.$t('commons.option_cannot_spread_pages'), '', {
confirmButtonText: this.$t('commons.confirm'),
callback: (action) => {
if (action === 'confirm') {
this.selectRows.forEach(loadCase => {
this._run(loadCase);
});
this.refreshStatus();
}
}
});
}else {
this.selectRows.forEach(loadCase => {
this._run(loadCase);
});
this.refreshStatus();
}
this.$refs.runMode.open();
},
run(loadCase) {
this._run(loadCase);