fix(接口自动化): 修复 GET请求query参数未生效;接口测试-场景,自定义请求有域名的执行报错;批量执行串行-集合报告执行失败,所有用例测试报告页面所选用例都只running,不是一个报告 等问题

This commit is contained in:
fit2-zhao 2021-04-25 18:28:11 +08:00 committed by fit2-zhao
parent b89f3856a6
commit fa8bb155fe
14 changed files with 181 additions and 82 deletions

View File

@ -182,7 +182,7 @@ public class ApiDefinitionController {
@PostMapping(value = "/import", consumes = {"multipart/form-data"})
@RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR)
public ApiDefinitionImport testCaseImport(@RequestPart(value = "file", required = false) MultipartFile file, @RequestPart("request") ApiTestImportRequest request) {
return apiDefinitionService.apiTestImport(file, request);//95821329-9eaa-4d2a-aa24-e6f912994716"
return apiDefinitionService.apiTestImport(file, request);
}
@PostMapping(value = "/export/{type}")

View File

@ -7,5 +7,6 @@ public class RunModeConfig {
private String mode;
private String reportType;
private String reportName;
private String reportId;
private boolean onSampleError;
}

View File

@ -287,8 +287,12 @@ public abstract class MsTestElement {
}
return "";
}
public boolean isURL(String str) {
try {
if (StringUtils.isEmpty(str)) {
return false;
}
new URL(str);
return true;
} catch (Exception e) {

View File

@ -204,7 +204,7 @@ public class MsHTTPSamplerProxy extends MsTestElement {
try {
if (config.isEffective(this.getProjectId())) {
HttpConfig httpConfig = getHttpConfig(config.getConfig().get(this.getProjectId()).getHttpConfig(), tree);
if (httpConfig == null) {
if (httpConfig == null && !isURL(this.getUrl())) {
MSException.throwException("未匹配到环境,请检查环境配置");
}
String url = httpConfig.getProtocol() + "://" + httpConfig.getSocket();
@ -228,10 +228,9 @@ public class MsHTTPSamplerProxy extends MsTestElement {
sampler.setProtocol(urlObject.getProtocol());
sampler.setPath(urlObject.getPath());
} else {
sampler.setDomain(httpConfig.getDomain());
//1.9 增加对Mock环境的判断
if (this.isMockEnvironment()) {
url = url = httpConfig.getProtocol() + "://" + httpConfig.getSocket() + "/mock/" + this.getProjectId();
url = httpConfig.getProtocol() + "://" + httpConfig.getSocket() + "/mock/" + this.getProjectId();
} else {
url = httpConfig.getProtocol() + "://" + httpConfig.getSocket();
}
@ -240,8 +239,14 @@ public class MsHTTPSamplerProxy extends MsTestElement {
if (StringUtils.isNotBlank(this.getPath())) {
envPath += this.getPath();
}
sampler.setPort(httpConfig.getPort());
if (StringUtils.isNotEmpty(httpConfig.getDomain())) {
sampler.setDomain(httpConfig.getDomain());
sampler.setProtocol(httpConfig.getProtocol());
} else {
sampler.setDomain("");
sampler.setProtocol("");
}
sampler.setPort(httpConfig.getPort());
sampler.setPath(envPath);
}
String envPath = sampler.getPath();
@ -258,7 +263,7 @@ public class MsHTTPSamplerProxy extends MsTestElement {
String port = sampler.getPort() != 80 ? ":" + sampler.getPort() : "";
path = sampler.getProtocol() + "://" + sampler.getDomain() + port + path;
}
sampler.setPath(path);
sampler.setProperty("HTTPSampler.path", path);
}
} else {
String url = this.getUrl();
@ -488,9 +493,6 @@ public class MsHTTPSamplerProxy extends MsTestElement {
}
}
}
if (httpConfig != null && (StringUtils.isEmpty(httpConfig.getProtocol()) || StringUtils.isEmpty(httpConfig.getSocket()))) {
return null;
}
// HTTP 环境中请求头
if (httpConfig != null) {
Arguments arguments = arguments(httpConfig.getHeaders());

View File

@ -13,17 +13,11 @@ public class HttpConfig {
private String socket;
private String domain;
private String protocol = "https";
private String defaultCondition;
private int port;
private boolean isMock;
private List<HttpConfigCondition> conditions;
private List<KeyValue> headers;
public boolean isNode(String type) {
return StringUtils.equals(defaultCondition, type);
}
public HttpConfig initHttpConfig(HttpConfigCondition configCondition) {
HttpConfig config = new HttpConfig();
BeanUtils.copyBean(config, configCondition);

View File

@ -39,7 +39,7 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
public final static String TEST_ID = "ms.test.id";
public final static String TEST_REPORT_NAME = "ms.test.report.name";
public final static String TEST_REPORT_ID = "ms.test.report.name";
private final static String THREAD_SPLIT = " ";
@ -72,7 +72,7 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
private String debugReportId;
// 只有合并报告是这个有值
private String reportName;
private String setReportId;
//获得控制台内容
private PrintStream oldPrintStream = System.out;
@ -143,7 +143,7 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
TestResult testResult = new TestResult();
testResult.setTestId(testId);
testResult.setTotal(queue.size());
testResult.setReportName(this.reportName);
testResult.setSetReportId(this.setReportId);
// 一个脚本里可能包含多个场景(ThreadGroup)所以要区分开key: 场景Id
final Map<String, ScenarioResult> scenarios = new LinkedHashMap<>();
@ -484,7 +484,7 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
private void setParam(BackendListenerContext context) {
this.testId = context.getParameter(TEST_ID);
this.reportName = context.getParameter(TEST_REPORT_NAME);
this.setReportId = context.getParameter(TEST_REPORT_ID);
this.runMode = context.getParameter("runMode");
this.debugReportId = context.getParameter("debugReportId");
if (StringUtils.isBlank(this.runMode)) {

View File

@ -129,7 +129,8 @@ public class JMeterService {
backendListener.setName(testId);
Arguments arguments = new Arguments();
if (config != null && config.getMode().equals("serial") && config.getReportType().equals("setReport")) {
arguments.addArgument(APIBackendListenerClient.TEST_REPORT_NAME, config.getReportName());
arguments.addArgument(APIBackendListenerClient.TEST_REPORT_ID, config.getReportId());
}
arguments.addArgument(APIBackendListenerClient.TEST_ID, testId);
if (StringUtils.isNotBlank(runMode)) {

View File

@ -12,7 +12,7 @@ public class TestResult {
private String testId;
private String reportName;
private String setReportId;
private String userId;

View File

@ -692,6 +692,21 @@ public class ApiAutomationService {
return testPlan.getJmx(jmeterHashTree);
}
private void checkEnv(RunScenarioRequest request, List<ApiScenarioWithBLOBs> apiScenarios) {
if (StringUtils.isNotBlank(request.getRunMode()) && StringUtils.equals(request.getRunMode(), ApiRunMode.SCENARIO.name())) {
StringBuilder builder = new StringBuilder();
for (ApiScenarioWithBLOBs apiScenarioWithBLOBs : apiScenarios) {
boolean haveEnv = checkScenarioEnv(apiScenarioWithBLOBs);
if (!haveEnv) {
builder.append(apiScenarioWithBLOBs.getName()).append("; ");
}
}
if (builder.length() > 0) {
MSException.throwException("场景:" + builder.toString() + "运行环境未配置,请检查!");
}
}
}
/**
* 场景测试并行执行
* 这种方法性能有问题 2021/04/12
@ -712,6 +727,9 @@ public class ApiAutomationService {
if (apiScenarios != null && apiScenarios.size() == 1 && (apiScenarios.get(0).getStepTotal() == null || apiScenarios.get(0).getStepTotal() == 0)) {
MSException.throwException((apiScenarios.get(0).getName() + "" + Translator.get("automation_exec_info")));
}
// 环境检查
this.checkEnv(request, apiScenarios);
if (StringUtils.isEmpty(request.getTriggerMode())) {
request.setTriggerMode(ReportTriggerMode.MANUAL.name());
}
@ -783,6 +801,9 @@ public class ApiAutomationService {
}
try {
boolean isFirst = true;
List<String> reportList = new ArrayList<>();
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
ApiScenarioReportMapper batchMapper = sqlSession.getMapper(ApiScenarioReportMapper.class);
for (ApiScenarioWithBLOBs item : apiScenarios) {
if (item.getStepTotal() == null || item.getStepTotal() == 0) {
// 只有一个场景且没有测试步骤则提示
@ -844,7 +865,8 @@ public class ApiAutomationService {
report = createScenarioReport(group.getName(), item.getId(), item.getName(), request.getTriggerMode() == null ? ReportTriggerMode.MANUAL.name() : request.getTriggerMode(),
request.getExecuteType(), item.getProjectId(), request.getReportUserID(), request.getConfig());
}
apiScenarioReportMapper.insert(report);
reportList.add(report.getId());
batchMapper.insert(report);
reportIds.add(group.getName());
}
group.setHashTree(scenarios);
@ -852,6 +874,14 @@ public class ApiAutomationService {
isFirst = false;
}
testPlan.toHashTree(jmeterHashTree, testPlan.getHashTree(), new ParameterConfig());
// 生成集成报告
if (request.getConfig() != null && request.getConfig().getMode().equals("serial")) {
request.getConfig().setReportId(UUID.randomUUID().toString());
APIScenarioReportResult report = createScenarioReport(request.getConfig().getReportId(), JSON.toJSONString(reportList), request.getConfig().getReportName(), request.getTriggerMode() == null ? ReportTriggerMode.MANUAL.name() : request.getTriggerMode(),
ExecuteType.Saved.name(), request.getProjectId(), request.getReportUserID(), request.getConfig());
batchMapper.insert(report);
}
sqlSession.flushStatements();
} catch (Exception ex) {
MSException.throwException(ex.getMessage());
}
@ -864,52 +894,46 @@ public class ApiAutomationService {
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
String definition = apiScenarioWithBLOBs.getScenarioDefinition();
MsScenario scenario = JSONObject.parseObject(definition, MsScenario.class);
boolean sign = true;
boolean isEnv = true;
Map<String, String> envMap = scenario.getEnvironmentMap();
if (envMap == null) {
sign = false;
} else {
Set<String> set = envMap.keySet();
if (CollectionUtils.isEmpty(set)) {
sign = false;
} else {
ScenarioEnv apiScenarioEnv = getApiScenarioEnv(definition);
// 所有请求非全路径检查环境
if (!apiScenarioEnv.getFullUrl()) {
try {
if (envMap == null || envMap.isEmpty()) {
isEnv = false;
} else {
Set<String> projectIds = apiScenarioEnv.getProjectIds();
if (CollectionUtils.isNotEmpty(set)) {
if (CollectionUtils.isNotEmpty(envMap.keySet())) {
for (String id : projectIds) {
String s = envMap.get(id);
if (StringUtils.isBlank(s)) {
sign = false;
isEnv = false;
break;
}
}
} else {
sign = false;
isEnv = false;
}
}
} catch (Exception e) {
sign = false;
isEnv = false;
LogUtil.error(e.getMessage(), e);
}
}
}
}
// 1.8 之前环境是 environmentId
if (!sign) {
if (!isEnv) {
String envId = scenario.getEnvironmentId();
if (StringUtils.isNotBlank(envId)) {
sign = true;
isEnv = true;
}
}
return sign;
return isEnv;
}
/**
* 场景执行
* 串行利用JMETER自身串行机制执行
*
* @param request
* @return
@ -926,18 +950,6 @@ public class ApiAutomationService {
});
List<ApiScenarioWithBLOBs> apiScenarios = extApiScenarioMapper.selectByIds(idStr.toString().substring(0, idStr.toString().length() - 1), "\"" + StringUtils.join(ids, ",") + "\"");
if (StringUtils.isNotBlank(request.getRunMode()) && StringUtils.equals(request.getRunMode(), ApiRunMode.SCENARIO.name())) {
StringBuilder builder = new StringBuilder();
for (ApiScenarioWithBLOBs apiScenarioWithBLOBs : apiScenarios) {
boolean haveEnv = checkScenarioEnv(apiScenarioWithBLOBs);
if (!haveEnv) {
builder.append(apiScenarioWithBLOBs.getName()).append("; ");
}
}
if (builder.length() > 0) {
MSException.throwException("场景:" + builder.toString() + "运行环境未配置,请检查!");
}
}
String runMode = ApiRunMode.SCENARIO.name();
if (StringUtils.isNotBlank(request.getRunMode()) && StringUtils.equals(request.getRunMode(), ApiRunMode.SCENARIO_PLAN.name())) {
runMode = ApiRunMode.SCENARIO_PLAN.name();
@ -945,6 +957,10 @@ public class ApiAutomationService {
if (StringUtils.isNotBlank(request.getRunMode()) && StringUtils.equals(request.getRunMode(), ApiRunMode.DEFINITION.name())) {
runMode = ApiRunMode.DEFINITION.name();
}
// 环境检查
this.checkEnv(request, apiScenarios);
// 调用执行方法
List<String> reportIds = new LinkedList<>();
try {
@ -1034,6 +1050,8 @@ public class ApiAutomationService {
} catch (Exception e) {
MSException.throwException(e.getMessage());
}
System.out.println(request.getTestElement().getJmx(hashTree));
// 调用执行方法
APIScenarioReportResult report = createScenarioReport(request.getId(), request.getScenarioId(), request.getScenarioName(), ReportTriggerMode.MANUAL.name(), request.getExecuteType(), request.getProjectId(),
SessionUtils.getUserId(), null);
@ -1296,7 +1314,9 @@ public class ApiAutomationService {
BeanUtils.copyBean(scenarioWithBLOBs, request);
scenarioWithBLOBs.setCreateTime(System.currentTimeMillis());
scenarioWithBLOBs.setUpdateTime(System.currentTimeMillis());
if (StringUtils.isEmpty(scenarioWithBLOBs.getStatus())) {
scenarioWithBLOBs.setStatus(APITestStatus.Underway.name());
}
scenarioWithBLOBs.setProjectId(apiTestImportRequest.getProjectId());
if (StringUtils.isEmpty(request.getPrincipal())) {
scenarioWithBLOBs.setPrincipal(Objects.requireNonNull(SessionUtils.getUser()).getId());

View File

@ -366,7 +366,9 @@ public class ApiDefinitionService {
BeanUtils.copyBean(saveReq, apiDefinition);
apiDefinition.setCreateTime(System.currentTimeMillis());
apiDefinition.setUpdateTime(System.currentTimeMillis());
if(StringUtils.isEmpty(apiDefinition.getStatus())) {
apiDefinition.setStatus(APITestStatus.Underway.name());
}
if (apiDefinition.getUserId() == null) {
apiDefinition.setUserId(Objects.requireNonNull(SessionUtils.getUser()).getId());
} else {

View File

@ -37,6 +37,7 @@ import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.text.DecimalFormat;
import java.util.*;
import java.util.stream.Collectors;
@Service
@Transactional(rollbackFor = Exception.class)
@ -343,11 +344,23 @@ public class ApiScenarioReportService {
private void margeReport(TestResult result, StringBuilder scenarioIds, StringBuilder scenarioNames, String runMode, String projectId, String userId, List<String> reportIds) {
// 合并生成一份报告
if (StringUtils.isNotEmpty(result.getReportName())) {
if (StringUtils.isNotEmpty(result.getSetReportId())) {
// 清理其他报告保留一份合并后的报告
this.deleteByIds(reportIds);
ApiScenarioReport report = createScenarioReport(scenarioIds.toString(), result.getReportName(), result.getError() > 0 ? "Error" : "Success", scenarioNames.toString().substring(0, scenarioNames.toString().length() - 1), runMode, projectId, userId);
ApiScenarioReport report = apiScenarioReportMapper.selectByPrimaryKey(result.getSetReportId());
report.setStatus(result.getError() > 0 ? "Error" : "Success");
if (StringUtils.isNotEmpty(userId)) {
report.setUserId(userId);
} else {
report.setUserId(SessionUtils.getUserId());
}
report.setTriggerMode(runMode);
report.setExecuteType(ExecuteType.Saved.name());
report.setProjectId(projectId);
report.setScenarioName(scenarioNames.toString().substring(0, scenarioNames.toString().length() - 1));
report.setScenarioId(scenarioIds.toString());
apiScenarioReportMapper.updateByPrimaryKey(report);
ApiScenarioReportDetail detail = new ApiScenarioReportDetail();
detail.setContent(JSON.toJSONString(result).getBytes(StandardCharsets.UTF_8));
detail.setReportId(report.getId());
@ -424,8 +437,27 @@ public class ApiScenarioReportService {
return report.getId();
}
public static List<String> getReportIds(String content) {
try {
return JSON.parseObject(content, List.class);
} catch (Exception e) {
return null;
}
}
public void delete(DeleteAPIReportRequest request) {
apiScenarioReportDetailMapper.deleteByPrimaryKey(request.getId());
// 补充逻辑如果是集成报告则把零时报告全部删除
ApiScenarioReport report = apiScenarioReportMapper.selectByPrimaryKey(request.getId());
if (report != null && StringUtils.isNotEmpty(report.getScenarioId())) {
List<String> list = getReportIds(report.getScenarioId());
if (CollectionUtils.isNotEmpty(list)) {
APIReportBatchRequest reportRequest = new APIReportBatchRequest();
reportRequest.setIds(list);
this.deleteAPIReportBatch(reportRequest);
}
}
apiScenarioReportMapper.deleteByPrimaryKey(request.getId());
}
@ -451,7 +483,18 @@ public class ApiScenarioReportService {
ids.removeAll(reportRequest.getUnSelectIds());
}
}
ApiScenarioReportExample example = new ApiScenarioReportExample();
example.createCriteria().andIdIn(reportRequest.getIds());
List<ApiScenarioReport> reportList = apiScenarioReportMapper.selectByExample(example);
// 取出可能是集成报告的ID 放入删除
reportList.forEach(item -> {
List<String> reportIds = getReportIds(item.getScenarioId());
if (CollectionUtils.isNotEmpty(reportIds)) {
reportRequest.getIds().addAll(reportIds);
}
});
List<String> myList = reportRequest.getIds().stream().distinct().collect(Collectors.toList());
reportRequest.setIds(myList);
//为预防数量太多调用删除方法时引起SQL过长的Bug此处采取分批执行的方式
//每次处理的数据数量
int handleCount = 7000;

View File

@ -99,13 +99,19 @@
this.isActive = !this.isActive;
},
getName(name) {
if (name && name.indexOf("^@~@^") != -1) {
if (name && name.indexOf("^@~@^") !== -1) {
let arr = name.split("^@~@^");
if (arr[arr.length - 1].indexOf("UUID=")) {
return arr[arr.length - 1].split("UUID=")[0];
}
if (arr[arr.length - 1] && arr[arr.length - 1].startsWith("UUID=")) {
return "";
}
return arr[arr.length - 1];
}
if (name && name.startsWith("UUID=")) {
return "";
}
return name;
}
},

View File

@ -35,6 +35,15 @@
</span>
<div v-if="apiCase.id" style="color: #999999;font-size: 12px">
<span v-if="api.protocol==='HTTP'">
<el-tag size="mini"
:style="{'background-color': getColor(true, apiCase.request.method), border: getColor(true, apiCase.request.method)}"
class="api-el-tag">
{{ apiCase.request.method }}
</el-tag>
{{apiCase.request.path}}
<br/>
</span>
<span>
{{ apiCase.createTime | timestampFormatDate }}
{{ apiCase.createUser }} {{ $t('api_test.definition.request.create_info') }}
@ -133,6 +142,7 @@
const requireComponent = require.context('@/business/components/xpack/', true, /\.vue$/);
const esbDefinition = (requireComponent != null && requireComponent.keys().length) > 0 ? requireComponent("./apidefinition/EsbDefinition.vue") : {};
const esbDefinitionResponse = (requireComponent != null && requireComponent.keys().length) > 0 ? requireComponent("./apidefinition/EsbDefinitionResponse.vue") : {};
import {API_METHOD_COLOUR} from "../../model/JsonData";
export default {
name: "ApiCaseItem",
@ -173,6 +183,7 @@
{name: this.$t('api_test.automation.batch_execute'), handleClick: this.handleRunBatch},
{name: this.$t('test_track.case.batch_edit_case'), handleClick: this.handleEditBatch}
],
methodColorMap: new Map(API_METHOD_COLOUR),
}
},
props: {
@ -208,6 +219,11 @@
handleRunBatch() {
this.$emit('batchRun');
},
getColor(enable, method) {
if (enable) {
return this.methodColorMap.get(method);
}
},
handleEditBatch() {
this.$emit('batchEditCase');
},
@ -429,6 +445,12 @@
z-index: 1;
}
.api-el-tag {
margin-left: 20px;
margin-right: 10px;
color: white;
}
.tag-item {
margin-right: 20px;
}

View File

@ -150,6 +150,12 @@
return this.$t("api_test.definition.api_path");
}
},
clearHisData() {
this.httpConfig.socket = undefined;
this.httpConfig.protocol = undefined;
this.httpConfig.port = undefined;
this.httpConfig.domain = undefined;
},
getDetails(row) {
if (row && row.type === "MODULE") {
if (row.details && row.details instanceof Array) {
@ -265,14 +271,12 @@
obj.details = this.condition.details ? JSON.parse(JSON.stringify(this.condition.details)) : this.condition.details;
}
this.httpConfig.conditions.unshift(obj);
this.httpConfig.socket = null;
this.httpConfig.protocol = null;
this.httpConfig.port = null;
this.httpConfig.domain = null;
this.clearHisData();
},
remove(row) {
const index = this.httpConfig.conditions.findIndex((d) => d.id === row.id);
this.httpConfig.conditions.splice(index, 1);
this.clearHisData();
},
copy(row) {
if (row.type === "NONE") {