This commit is contained in:
chenjianxing 2021-02-01 10:58:59 +08:00
commit 26ae02300e
24 changed files with 148 additions and 63 deletions

View File

@ -7,9 +7,9 @@
> [English](README-EN.md) | 中文
| 下载持续测试白皮书 |
| 《持续测试白皮书 v1.0》成功发布 |
| ------------------------------------------------------------------------------------------------------------ |
| 《持续测试白皮书 v1.0》由“软件质量报道”公众号和MeterSphere开源项目组共同编写而成。采用贴近企业实践的视角进行组织,编写团队结合自身对软件测试发展历程以及持续测试理念的理解,并积极听取行业内其他专家理念,在此基础上编写而成。全文从持续测试产生的背景和价值、企业如何实践持续测试以及持续测试成熟度模型这三个方面展开,分别介绍了持续测试产生的原因、持续测试落地的关键要素和持续测试成熟度模型的构成、评判等方面内容。下载链接: [https://jinshuju.net/f/KqFUhq](https://jinshuju.net/f/KqFUhq) |
| “软件质量报道”公众号和MeterSphere开源项目组历时四个月,结合自己的经验和业界各方面的专家反馈,完成《持续测试白皮书 v1.0》的编写工作。期待本白皮书可以帮助业界更多企业和专业用户在日常工作中更好地将“持续测试”理念付诸实践。下载链接: [https://jinshuju.net/f/KqFUhq](https://jinshuju.net/f/KqFUhq) |
MeterSphere 是一站式开源持续测试平台涵盖测试跟踪、接口测试、性能测试、团队协作等功能兼容JMeter 等开源标准,有效助力开发和测试团队充分利用云弹性进行高度可扩展的自动化测试,加速高质量软件的交付。

View File

@ -29,6 +29,12 @@ public class ApiModuleController {
return apiModuleService.getNodeTreeByProjectId(projectId,protocol);
}
@GetMapping("/getModuleByName/{projectId}/{protocol}")
public ApiModule getModuleByName(@PathVariable String projectId,@PathVariable String protocol) {
checkPermissionService.checkProjectOwner(projectId);
return apiModuleService.getModuleByName(projectId,protocol);
}
@GetMapping("/list/plan/{planId}/{protocol}")
public List<ApiModuleDTO> getNodeByPlanId(@PathVariable String planId, @PathVariable String protocol) {
checkPermissionService.checkTestPlanOwner(planId);

View File

@ -256,25 +256,21 @@ public abstract class MsTestElement {
return getRootParent(element.getParent());
}
protected String getParentName(MsTestElement element, ParameterConfig config) {
if (element != null) {
MsTestElement parent = this.getRootParent(element);
if (parent != null) {
if (MsTestElementConstants.LoopController.name().equals(parent.getType())) {
MsLoopController loopController = (MsLoopController) parent;
if (StringUtils.equals(loopController.getLoopType(), LoopConstants.WHILE.name()) && loopController.getWhileController() != null) {
return "While 循环-" + "${LoopCounterConfigXXX}";
}
if (StringUtils.equals(loopController.getLoopType(), LoopConstants.FOREACH.name()) && loopController.getForEachController() != null) {
return "ForEach 循环-" + "${LoopCounterConfigXXX}";
}
if (StringUtils.equals(loopController.getLoopType(), LoopConstants.LOOP_COUNT.name()) && loopController.getCountController() != null) {
return "次数循环-" + "${LoopCounterConfigXXX}";
}
protected String getParentName(MsTestElement parent, ParameterConfig config) {
if (parent != null) {
if (MsTestElementConstants.LoopController.name().equals(parent.getType())) {
MsLoopController loopController = (MsLoopController) parent;
if (StringUtils.equals(loopController.getLoopType(), LoopConstants.WHILE.name()) && loopController.getWhileController() != null) {
return "While 循环-" + "${LoopCounterConfigXXX}";
}
if (StringUtils.equals(loopController.getLoopType(), LoopConstants.FOREACH.name()) && loopController.getForEachController() != null) {
return "ForEach 循环-" + "${LoopCounterConfigXXX}";
}
if (StringUtils.equals(loopController.getLoopType(), LoopConstants.LOOP_COUNT.name()) && loopController.getCountController() != null) {
return "次数循环-" + "${LoopCounterConfigXXX}";
}
return parent.getName();
}
return element.getName();
return parent.getName();
} else if (config != null && StringUtils.isNotEmpty(config.getStep())) {
if (MsTestElementConstants.SCENARIO.name().equals(config.getStepType())) {
return config.getStep();

View File

@ -177,6 +177,10 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
} else if (StringUtils.equals(this.runMode, ApiRunMode.JENKINS.name())) {
apiDefinitionService.addResult(testResult);
apiDefinitionExecResultService.saveApiResult(testResult, ApiRunMode.DEFINITION.name());
apiTestService.changeStatus(testId, APITestStatus.Completed);
report = apiReportService.getRunningReport(testResult.getTestId());
apiReportService.complete(testResult, report);
apiDefinitionExecResultService.saveApiResult(testResult, ApiRunMode.API_PLAN.name());
} else if (StringUtils.equalsAny(this.runMode, ApiRunMode.API_PLAN.name(), ApiRunMode.SCHEDULE_API_PLAN.name())) {
apiDefinitionService.addResult(testResult);

View File

@ -702,7 +702,10 @@ public class ApiDefinitionService {
public String getResourceId(SwaggerUrlRequest swaggerUrlRequest) {
SwaggerUrlProjectExample swaggerUrlProjectExample = new SwaggerUrlProjectExample();
SwaggerUrlProjectExample.Criteria criteria = swaggerUrlProjectExample.createCriteria();
criteria.andProjectIdEqualTo(swaggerUrlRequest.getProjectId()).andSwaggerUrlEqualTo(swaggerUrlRequest.getSwaggerUrl()).andModuleIdEqualTo(swaggerUrlRequest.getModuleId());
criteria.andProjectIdEqualTo(swaggerUrlRequest.getProjectId()).andSwaggerUrlEqualTo(swaggerUrlRequest.getSwaggerUrl());
if (StringUtils.isNotBlank(swaggerUrlRequest.getModuleId())) {
criteria.andModuleIdEqualTo(swaggerUrlRequest.getModuleId());
}
List<SwaggerUrlProject> list = swaggerUrlProjectMapper.selectByExample(swaggerUrlProjectExample);
String resourceId = "";
if (list.size() == 1) {

View File

@ -332,5 +332,27 @@ public class ApiModuleService extends NodeTreeService<ApiModuleDTO> {
sqlSession.flushStatements();
}
public ApiModule getModuleByName(String projectId, String protocol) {
ApiModuleExample example = new ApiModuleExample();
ApiModuleExample.Criteria criteria = example.createCriteria();
criteria.andNameEqualTo("bug")
.andProjectIdEqualTo(projectId).andProtocolEqualTo(protocol);
List<ApiModule> modules = apiModuleMapper.selectByExample(example);
if (CollectionUtils.isNotEmpty(modules)) {
return modules.get(0);
} else {
ApiModule node = new ApiModule();
node.setName("bug");
node.setLevel(1);
node.setPos(0.0);
node.setParentId(null);
node.setProjectId(projectId);
node.setProtocol(protocol);
node.setCreateTime(System.currentTimeMillis());
node.setUpdateTime(System.currentTimeMillis());
node.setId(UUID.randomUUID().toString());
apiModuleMapper.insertSelective(node);
return node;
}
}
}

View File

@ -28,7 +28,6 @@ import io.metersphere.base.mapper.ApiTestMapper;
import io.metersphere.base.mapper.ext.ExtApiScenarioMapper;
import io.metersphere.commons.utils.BeanUtils;
import io.metersphere.commons.utils.DateUtils;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.commons.utils.SessionUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
@ -42,7 +41,6 @@ import javax.annotation.Resource;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.net.URL;
import java.nio.channels.FileChannel;
import java.util.*;
@ -116,18 +114,6 @@ public class HistoricalDataUpgradeService {
if (request instanceof HttpRequest) {
element = new MsHTTPSamplerProxy();
HttpRequest request1 = (HttpRequest) request;
if (StringUtils.isEmpty(request1.getPath()) && StringUtils.isNotEmpty(request1.getUrl())) {
try {
URL urlObject = new URL(request1.getUrl());
String envPath = StringUtils.equals(urlObject.getPath(), "/") ? "" : urlObject.getPath();
request1.setPath(envPath);
request1.setUrl(null);
} catch (Exception ex) {
LogUtil.error(ex.getMessage());
}
} else {
request1.setUrl(null);
}
if (request1.getBody() != null) {
request1.getBody().setBinary(new ArrayList<>());
if (request1.getBody().isOldKV()) {
@ -136,21 +122,21 @@ public class HistoricalDataUpgradeService {
if ("json".equals(request1.getBody().getFormat())) {
if ("Raw".equals(request1.getBody().getType())) {
request1.getBody().setType(Body.JSON);
}
if (CollectionUtils.isEmpty(request1.getHeaders())) {
List<KeyValue> headers = new LinkedList<>();
headers.add(new KeyValue("Content-Type", "application/json"));
request1.setHeaders(headers);
} else {
boolean isJsonType = false;
for (KeyValue keyValue : request1.getHeaders()) {
if ("Content-Type".equals(keyValue.getName())) {
isJsonType = true;
break;
if (CollectionUtils.isEmpty(request1.getHeaders())) {
List<KeyValue> headers = new LinkedList<>();
headers.add(new KeyValue("Content-Type", "application/json"));
request1.setHeaders(headers);
} else {
boolean isJsonType = false;
for (KeyValue keyValue : request1.getHeaders()) {
if ("Content-Type".equals(keyValue.getName())) {
isJsonType = true;
break;
}
}
if (!isJsonType) {
request1.getHeaders().set(request1.getHeaders().size() - 1, new KeyValue("Content-Type", "application/json"));
}
}
if (!isJsonType) {
request1.getHeaders().set(request1.getHeaders().size() - 1, new KeyValue("Content-Type", "application/json"));
}
}
}
@ -161,6 +147,13 @@ public class HistoricalDataUpgradeService {
BeanUtils.copyBean(element, request1);
((MsHTTPSamplerProxy) element).setProtocol(RequestType.HTTP);
((MsHTTPSamplerProxy) element).setArguments(request1.getParameters());
if (StringUtils.isNotEmpty(request1.getPath()) && request1.isUseEnvironment()) {
((MsHTTPSamplerProxy) element).setPath(request1.getPath());
((MsHTTPSamplerProxy) element).setUrl(null);
} else {
((MsHTTPSamplerProxy) element).setPath(null);
((MsHTTPSamplerProxy) element).setUrl(request1.getUrl());
}
List<KeyValue> keyValues = new LinkedList<>();
keyValues.add(new KeyValue("", ""));
((MsHTTPSamplerProxy) element).setRest(keyValues);
@ -318,9 +311,6 @@ public class HistoricalDataUpgradeService {
}
private void createApiScenarioWithBLOBs(SaveHistoricalDataUpgrade saveHistoricalDataUpgrade, String id, String name, int total, String scenarioDefinition, ApiScenarioMapper mapper, int num) {
if (StringUtils.isEmpty(name)) {
name = "默认名称-" + DateUtils.getTimeStr(System.currentTimeMillis());
}
ApiScenarioWithBLOBs scenario = getScenario(id, mapper);
if (scenario != null) {
scenario.setName(name);
@ -377,6 +367,11 @@ public class HistoricalDataUpgradeService {
if (CollectionUtils.isNotEmpty(scenarios)) {
// 批量处理
for (Scenario scenario : scenarios) {
if (StringUtils.isEmpty(scenario.getName())) {
scenario.setName("默认名称-" + DateUtils.getTimeStr(System.currentTimeMillis()));
}
scenario.setId(test.getId() + "=" + scenario.getId());
scenario.setName(test.getName() + "_" + scenario.getName());
MsScenario scenario1 = createScenario(scenario);
String scenarioDefinition = JSON.toJSONString(scenario1);
num++;

View File

@ -15,6 +15,7 @@ import io.metersphere.track.dto.TestPlanDTOWithMetric;
import io.metersphere.track.request.testcase.PlanCaseRelevanceRequest;
import io.metersphere.track.request.testcase.QueryTestPlanRequest;
import io.metersphere.track.request.testplan.AddTestPlanRequest;
import io.metersphere.track.request.testplan.TestplanRunRequest;
import io.metersphere.track.request.testplancase.TestCaseRelevanceRequest;
import io.metersphere.track.service.TestPlanProjectService;
import io.metersphere.track.service.TestPlanService;
@ -136,4 +137,8 @@ public class TestPlanController {
Page<Object> page = PageHelper.startPage(goPage, pageSize, true);
return PageUtils.setPageInfo(page, testPlanProjectService.getProjectByPlanId(request));
}
@PostMapping("/testplan/jenkins")
public void runJenkins(@RequestBody TestplanRunRequest testplanRunRequest){
testPlanService.run(testplanRunRequest.getTestPlanID(),testplanRunRequest.getProjectID(),testplanRunRequest.getUserId(),testplanRunRequest.getTriggerMode());
}
}

View File

@ -0,0 +1,15 @@
package io.metersphere.track.request.testplan;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class TestplanRunRequest {
private String testPlanID;
private String projectID;
private String userId;
private String triggerMode;
}

@ -1 +1 @@
Subproject commit 26d36f3f81e889f58eed7c6903252a129b301d98
Subproject commit b35af517d888268abd3a8f2b58d6ea94335138a5

View File

@ -0,0 +1 @@
ALTER TABLE api_scenario MODIFY COLUMN id VARCHAR (120);

View File

@ -0,0 +1,6 @@
ALTER TABLE swagger_url_project
MODIFY COLUMN id VARCHAR(120);
ALTER TABLE swagger_url_project
MODIFY COLUMN project_id VARCHAR(120);
ALTER TABLE swagger_url_project
MODIFY COLUMN mode_id VARCHAR(120);

View File

@ -149,6 +149,7 @@
const index = this.variables.findIndex(d => d.id === row);
this.variables.splice(index, 1);
})
this.selection = [];
}
}
}

View File

@ -250,9 +250,19 @@
data.method = data.protocol;
}
},
saveApi(row) {
addModule(row) {
let url = '/api/module/getModuleByName/' + getCurrentProjectID() + "/" + this.api.protocol;
this.$get(url, response => {
if (response.data) {
this.saveApi(row, response.data);
}
});
},
saveApi(row, module) {
let data = this.api;
data.name = this.apiCase.name;
data.moduleId = module.id;
data.modulePath = '/bug';
this.setParameters(data);
let bodyFiles = this.getBodyUploadFiles(data);
this.$fileUpload("/api/definition/create", null, bodyFiles, data, () => {
@ -296,7 +306,7 @@
},
saveTestCase(row) {
if (this.api.saved) {
this.saveApi(row);
this.addModule(row);
} else {
this.saveCase(row);
}

View File

@ -113,11 +113,11 @@ export default {
showEnvironmentSelect: true,
modeOptions: [{
id: 'fullCoverage',
name: '全量覆盖'
name: this.$t('commons.cover')
},
{
id: 'incrementalMerge',
name: '增量合并'
name: this.$t('commons.not_cover')
}],
protocol: "",
platforms: [
@ -152,8 +152,8 @@ export default {
formData: {
file: undefined,
swaggerUrl: '',
modeId: '',
moduleId: ''
modeId: this.$t('commons.not_cover'),
moduleId: '',
},
rules: {},
currentModule: {},

View File

@ -143,7 +143,12 @@
copyRow(row) {
let obj = JSON.parse(JSON.stringify(row));
obj.id = getUUID();
this.request.hashTree.push(obj);
const index = this.request.hashTree.findIndex(d => d.id === row.id);
if (index != -1) {
this.request.hashTree.splice(index, 0, obj);
} else {
this.request.hashTree.push(obj);
}
this.reload();
},
reload() {

View File

@ -274,4 +274,7 @@ export default {
.el-row {
margin-bottom: 10px;
}
.el-button {
margin-left: 10px;
}
</style>

View File

@ -295,4 +295,8 @@ export default {
.el-row {
margin-bottom: 10px;
}
.el-button {
margin-left: 10px;
}
</style>

View File

@ -308,4 +308,7 @@ export default {
.el-row {
margin-bottom: 10px;
}
.el-button {
margin-left: 10px;
}
</style>

View File

@ -313,4 +313,7 @@ export default {
.el-row {
margin-bottom: 10px;
}
.el-button {
margin-left: 10px;
}
</style>

View File

@ -252,9 +252,6 @@ export default {
activated() {
this.initTableData();
},
created() {
this.list()
},
methods: {
create() {
this.dialogOrgAddVisible = true;

View File

@ -1,5 +1,7 @@
export default {
commons: {
cover:'Cover',
not_cover:'Not Cover',
import_mode: 'Import mode',
import_module: 'Import module',
please_fill_in_the_template: 'Please fill in the template',

View File

@ -1,5 +1,7 @@
export default {
commons: {
cover:'覆盖',
not_cover:'不覆盖',
import_mode: '导入模式',
import_module: '导入模块',
please_fill_in_the_template: '请填写模版内容',

View File

@ -1,5 +1,7 @@
export default {
commons: {
cover:'覆蓋',
not_cover:'不覆蓋',
import_mode: '導入模式',
import_module: '導入模塊',
please_fill_in_the_template: '請填寫模版內容',