This commit is contained in:
wenyann 2020-06-16 10:37:37 +08:00
commit 284a6adda1
41 changed files with 684 additions and 152 deletions

View File

@ -1,8 +1,11 @@
package io.metersphere.api.dto;
import io.metersphere.api.dto.scenario.Scenario;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Setter
@Getter
public class SaveAPITestRequest {
@ -13,5 +16,5 @@ public class SaveAPITestRequest {
private String name;
private String scenarioDefinition;
private List<Scenario> scenarioDefinition;
}

View File

@ -0,0 +1,12 @@
package io.metersphere.api.dto.scenario;
import lombok.Data;
import java.util.List;
@Data
public class Body {
private String type;
private String raw;
private List<KeyValue> kvs;
}

View File

@ -0,0 +1,9 @@
package io.metersphere.api.dto.scenario;
import lombok.Data;
@Data
public class KeyValue {
private String name;
private String value;
}

View File

@ -0,0 +1,19 @@
package io.metersphere.api.dto.scenario;
import io.metersphere.api.dto.scenario.assertions.Assertions;
import io.metersphere.api.dto.scenario.extract.Extract;
import lombok.Data;
import java.util.List;
@Data
public class Request {
private String name;
private String url;
private String method;
private List<KeyValue> parameters;
private List<KeyValue> headers;
private Body body;
private Assertions assertions;
private Extract extract;
}

View File

@ -0,0 +1,14 @@
package io.metersphere.api.dto.scenario;
import lombok.Data;
import java.util.List;
@Data
public class Scenario {
private String name;
private String url;
private List<KeyValue> variables;
private List<KeyValue> headers;
private List<Request> requests;
}

View File

@ -0,0 +1,14 @@
package io.metersphere.api.dto.scenario.assertions;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
public class AssertionDuration extends AssertionType {
private long value;
public AssertionDuration() {
setType(AssertionType.DURATION);
}
}

View File

@ -0,0 +1,16 @@
package io.metersphere.api.dto.scenario.assertions;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
public class AssertionRegex extends AssertionType {
private String subject;
private String expression;
private String description;
public AssertionRegex() {
setType(AssertionType.REGEX);
}
}

View File

@ -0,0 +1,12 @@
package io.metersphere.api.dto.scenario.assertions;
import lombok.Data;
@Data
public class AssertionType {
public final static String REGEX = "Regex";
public final static String DURATION = "Duration";
public final static String TEXT = "Text";
private String type;
}

View File

@ -0,0 +1,11 @@
package io.metersphere.api.dto.scenario.assertions;
import lombok.Data;
import java.util.List;
@Data
public class Assertions {
private List<AssertionRegex> regex;
private AssertionDuration duration;
}

View File

@ -0,0 +1,12 @@
package io.metersphere.api.dto.scenario.extract;
import lombok.Data;
import java.util.List;
@Data
public class Extract {
private List<ExtractRegex> regex;
private List<ExtractJSONPath> json;
private List<ExtractXPath> xpath;
}

View File

@ -0,0 +1,13 @@
package io.metersphere.api.dto.scenario.extract;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
public class ExtractCommon extends ExtractType {
private String variable;
private String value; // value: ${variable}
private String expression;
private String description;
}

View File

@ -0,0 +1,12 @@
package io.metersphere.api.dto.scenario.extract;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
public class ExtractJSONPath extends ExtractCommon {
public ExtractJSONPath() {
setType(ExtractType.JSON_PATH);
}
}

View File

@ -0,0 +1,12 @@
package io.metersphere.api.dto.scenario.extract;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
public class ExtractRegex extends ExtractCommon {
public ExtractRegex() {
setType(ExtractType.REGEX);
}
}

View File

@ -0,0 +1,12 @@
package io.metersphere.api.dto.scenario.extract;
import lombok.Data;
@Data
public class ExtractType {
public final static String REGEX = "Regex";
public final static String JSON_PATH = "JSONPath";
public final static String XPATH = "XPath";
private String type;
}

View File

@ -0,0 +1,12 @@
package io.metersphere.api.dto.scenario.extract;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
public class ExtractXPath extends ExtractCommon {
public ExtractXPath() {
setType(ExtractType.XPATH);
}
}

View File

@ -1,5 +1,6 @@
package io.metersphere.api.service;
import com.alibaba.fastjson.JSONObject;
import io.metersphere.api.dto.APITestResult;
import io.metersphere.api.dto.QueryAPITestRequest;
import io.metersphere.api.dto.SaveAPITestRequest;
@ -25,6 +26,7 @@ import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.List;
import java.util.Objects;
import java.util.Random;
import java.util.UUID;
import java.util.stream.Collectors;
@ -73,10 +75,17 @@ public class APITestService {
}
public void copy(SaveAPITestRequest request) {
request.setName(request.getName() + " Copy");
try {
checkNameExist(request);
} catch (Exception e) {
request.setName(request.getName() + " " + new Random().nextInt(1000));
}
// copy test
ApiTestWithBLOBs copy = get(request.getId());
copy.setId(UUID.randomUUID().toString());
copy.setName(copy.getName() + " Copy");
copy.setName(request.getName());
copy.setCreateTime(System.currentTimeMillis());
copy.setUpdateTime(System.currentTimeMillis());
copy.setStatus(APITestStatus.Saved.name());
@ -96,6 +105,10 @@ public class APITestService {
return apiTestMapper.selectByPrimaryKey(id);
}
public List<ApiTest> getApiTestByProjectId(String projectId) {
return extApiTestMapper.getApiTestByProjectId(projectId);
}
public void delete(String testId) {
deleteFileByTestId(testId);
apiReportService.deleteByTestId(testId);
@ -138,7 +151,7 @@ public class APITestService {
test.setId(request.getId());
test.setName(request.getName());
test.setProjectId(request.getProjectId());
test.setScenarioDefinition(request.getScenarioDefinition());
test.setScenarioDefinition(JSONObject.toJSONString(request.getScenarioDefinition()));
test.setUpdateTime(System.currentTimeMillis());
test.setStatus(APITestStatus.Saved.name());
apiTestMapper.updateByPrimaryKeySelective(test);
@ -151,7 +164,7 @@ public class APITestService {
test.setId(request.getId());
test.setName(request.getName());
test.setProjectId(request.getProjectId());
test.setScenarioDefinition(request.getScenarioDefinition());
test.setScenarioDefinition(JSONObject.toJSONString(request.getScenarioDefinition()));
test.setCreateTime(System.currentTimeMillis());
test.setUpdateTime(System.currentTimeMillis());
test.setStatus(APITestStatus.Saved.name());
@ -194,7 +207,4 @@ public class APITestService {
}
}
public List<ApiTest> getApiTestByProjectId(String projectId) {
return extApiTestMapper.getApiTestByProjectId(projectId);
}
}

View File

@ -36,6 +36,9 @@
#{nodeId}
</foreach>
</if>
<if test="request.status != null">
and test_plan_test_case.status = #{request.status}
</if>
<if test="request.executor != null">
and test_plan_test_case.executor = #{request.executor}
</if>

View File

@ -0,0 +1,18 @@
package io.metersphere.commons.utils;
import java.math.BigDecimal;
public class MathUtils {
/**
* 获取百分比
* 保留一位小数
* @param value
* @return
*/
public static double getPercentWithDecimal(double value) {
return new BigDecimal(value * 100)
.setScale(1, BigDecimal.ROUND_HALF_UP)
.doubleValue();
}
}

View File

@ -0,0 +1,36 @@
package io.metersphere.track.Factory;
import io.metersphere.track.domain.*;
import io.metersphere.track.dto.TestPlanDTO;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.List;
public class ReportComponentFactory {
public static ReportComponent createComponent(String componentId, TestPlanDTO testPlan) {
if (StringUtils.equals("1", componentId)) {
return new ReportBaseInfoComponent(testPlan);
} else if (StringUtils.equals("2", componentId)) {
return new ReportResultComponent(testPlan);
} else if (StringUtils.equals("3", componentId)) {
return new ReportResultChartComponent(testPlan);
} else if (StringUtils.equals("4", componentId)) {
return new ReportFailureResultComponent(testPlan);
}
return null;
}
public static List<ReportComponent> createComponents(List<String> componentIds, TestPlanDTO testPlan) {
List<ReportComponent> components = new ArrayList<>();
componentIds.forEach(id -> {
ReportComponent component = createComponent(id, testPlan);
if (component != null) {
components.add(component);
}
});
return components;
}
}

View File

@ -0,0 +1,30 @@
package io.metersphere.track.domain;
import io.metersphere.track.dto.TestCaseReportMetricDTO;
import io.metersphere.track.dto.TestPlanCaseDTO;
import io.metersphere.track.dto.TestPlanDTO;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
public class ReportBaseInfoComponent extends ReportComponent {
private Set<String> executorsSet = new HashSet<>();
public ReportBaseInfoComponent(TestPlanDTO testPlan) {
super(testPlan);
componentId = "1";
}
@Override
public void readRecord(TestPlanCaseDTO testCase) {
executorsSet.add(testCase.getExecutor());
}
@Override
public void afterBuild(TestCaseReportMetricDTO testCaseReportMetric) {
testCaseReportMetric.setProjectName(testPlan.getProjectName());
testCaseReportMetric.setPrincipal(testPlan.getPrincipal());
testCaseReportMetric.setExecutors(new ArrayList<>(this.executorsSet));
}
}

View File

@ -0,0 +1,15 @@
package io.metersphere.track.domain;
import io.metersphere.track.dto.TestCaseReportMetricDTO;
import io.metersphere.track.dto.TestPlanCaseDTO;
import io.metersphere.track.dto.TestPlanDTO;
public abstract class ReportComponent {
protected String componentId;
protected TestPlanDTO testPlan;
public ReportComponent(TestPlanDTO testPlan) {
this.testPlan = testPlan;
}
public abstract void readRecord(TestPlanCaseDTO testCase);
public abstract void afterBuild(TestCaseReportMetricDTO testCaseReportMetric);
}

View File

@ -0,0 +1,30 @@
package io.metersphere.track.domain;
import io.metersphere.commons.constants.TestPlanTestCaseStatus;
import io.metersphere.track.dto.TestCaseReportMetricDTO;
import io.metersphere.track.dto.TestPlanCaseDTO;
import io.metersphere.track.dto.TestPlanDTO;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.List;
public class ReportFailureResultComponent extends ReportComponent {
private List<TestPlanCaseDTO> failureTestCases = new ArrayList<>();
public ReportFailureResultComponent(TestPlanDTO testPlan) {
super(testPlan);
componentId = "4";
}
@Override
public void readRecord(TestPlanCaseDTO testCase) {
if (StringUtils.equals(testCase.getStatus(), TestPlanTestCaseStatus.Failure.name())) {
this.failureTestCases.add(testCase);
}
}
@Override
public void afterBuild(TestCaseReportMetricDTO testCaseReportMetric) {
testCaseReportMetric.setFailureTestCases(failureTestCases);
}
}

View File

@ -0,0 +1,41 @@
package io.metersphere.track.domain;
import io.metersphere.track.dto.TestCaseReportMetricDTO;
import io.metersphere.track.dto.TestCaseReportStatusResultDTO;
import io.metersphere.track.dto.TestPlanCaseDTO;
import io.metersphere.track.dto.TestPlanDTO;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
public class ReportResultChartComponent extends ReportComponent {
Map<String, TestCaseReportStatusResultDTO> reportStatusResultMap = new HashMap<>();
public ReportResultChartComponent(TestPlanDTO testPlan) {
super(testPlan);
componentId = "3";
}
@Override
public void readRecord(TestPlanCaseDTO testCase) {
getStatusResultMap(reportStatusResultMap, testCase);
}
@Override
public void afterBuild(TestCaseReportMetricDTO testCaseReportMetric) {
testCaseReportMetric.setExecuteResult(new ArrayList<>(reportStatusResultMap.values()));
}
private void getStatusResultMap(Map<String, TestCaseReportStatusResultDTO> reportStatusResultMap, TestPlanCaseDTO testCase) {
TestCaseReportStatusResultDTO statusResult = reportStatusResultMap.get(testCase.getStatus());
if (statusResult == null) {
statusResult = new TestCaseReportStatusResultDTO();
statusResult.setStatus(testCase.getStatus());
statusResult.setCount(0);
}
statusResult.setCount(statusResult.getCount() + 1);
reportStatusResultMap.put(testCase.getStatus(), statusResult);
}
}

View File

@ -0,0 +1,105 @@
package io.metersphere.track.domain;
import com.alibaba.fastjson.JSON;
import io.metersphere.base.domain.TestCaseNode;
import io.metersphere.base.domain.TestCaseNodeExample;
import io.metersphere.base.mapper.TestCaseNodeMapper;
import io.metersphere.commons.constants.TestPlanTestCaseStatus;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.commons.utils.MathUtils;
import io.metersphere.track.dto.*;
import io.metersphere.track.service.TestCaseNodeService;
import org.apache.commons.lang3.StringUtils;
import java.util.*;
public class ReportResultComponent extends ReportComponent {
private List<TestCaseNodeDTO> nodeTrees = new ArrayList<>();
private Map<String, Set<String>> childIdMap = new HashMap<>();
private Map<String, TestCaseReportModuleResultDTO> moduleResultMap = new HashMap<>();
public ReportResultComponent(TestPlanDTO testPlan) {
super(testPlan);
componentId = "2";
init();
}
public void init() {
TestCaseNodeService testCaseNodeService = (TestCaseNodeService) CommonBeanFactory.getBean("testCaseNodeService");
TestCaseNodeMapper testCaseNodeMapper = (TestCaseNodeMapper) CommonBeanFactory.getBean("testCaseNodeMapper");
TestCaseNodeExample testCaseNodeExample = new TestCaseNodeExample();
testCaseNodeExample.createCriteria().andProjectIdEqualTo(testPlan.getProjectId());
List<TestCaseNode> nodes = testCaseNodeMapper.selectByExample(testCaseNodeExample);
nodeTrees = testCaseNodeService.getNodeTrees(nodes);
nodeTrees.forEach(item -> {
Set<String> childIds = new HashSet<>();
getChildIds(item, childIds);
childIdMap.put(item.getId(), childIds);
});
}
@Override
public void readRecord(TestPlanCaseDTO testCase) {
getModuleResultMap(childIdMap, moduleResultMap, testCase, nodeTrees);
}
@Override
public void afterBuild(TestCaseReportMetricDTO testCaseReportMetric) {
nodeTrees.forEach(rootNode -> {
TestCaseReportModuleResultDTO moduleResult = moduleResultMap.get(rootNode.getId());
if (moduleResult != null) {
moduleResult.setModuleName(rootNode.getName());
}
});
for (TestCaseReportModuleResultDTO moduleResult : moduleResultMap.values()) {
moduleResult.setPassRate(MathUtils.getPercentWithDecimal(moduleResult.getPassCount()*1.0f/moduleResult.getCaseCount()));
if (moduleResult.getCaseCount() <= 0) {
moduleResultMap.remove(moduleResult.getModuleId());
}
}
testCaseReportMetric.setModuleExecuteResult(new ArrayList<>(moduleResultMap.values()));
}
private void getChildIds(TestCaseNodeDTO rootNode, Set<String> childIds) {
childIds.add(rootNode.getId());
List<TestCaseNodeDTO> children = rootNode.getChildren();
if(children != null) {
Iterator<TestCaseNodeDTO> iterator = children.iterator();
while(iterator.hasNext()){
getChildIds(iterator.next(), childIds);
}
}
}
private void getModuleResultMap(Map<String, Set<String>> childIdMap, Map<String, TestCaseReportModuleResultDTO> moduleResultMap, TestPlanCaseDTO testCase, List<TestCaseNodeDTO> nodeTrees) {
childIdMap.forEach((rootNodeId, childIds) -> {
if (childIds.contains(testCase.getNodeId())) {
TestCaseReportModuleResultDTO moduleResult = moduleResultMap.get(rootNodeId);
if (moduleResult == null) {
moduleResult = new TestCaseReportModuleResultDTO();
moduleResult.setCaseCount(0);
moduleResult.setPassCount(0);
moduleResult.setIssuesCount(0);
moduleResult.setModuleId(rootNodeId);
}
moduleResult.setCaseCount(moduleResult.getCaseCount() + 1);
if (StringUtils.equals(testCase.getStatus(), TestPlanTestCaseStatus.Pass.name())) {
moduleResult.setPassCount(moduleResult.getPassCount() + 1);
}
if (StringUtils.isNotBlank(testCase.getIssues())) {
if (JSON.parseObject(testCase.getIssues()).getBoolean("hasIssues")) {
moduleResult.setIssuesCount(moduleResult.getIssuesCount() + 1);
};
}
moduleResultMap.put(rootNodeId, moduleResult);
return;
}
});
}
}

View File

@ -1,5 +1,9 @@
package io.metersphere.track.dto;
import io.metersphere.track.domain.ReportBaseInfoComponent;
import io.metersphere.track.domain.ReportFailureResultComponent;
import io.metersphere.track.domain.ReportResultChartComponent;
import io.metersphere.track.domain.ReportResultComponent;
import lombok.Getter;
import lombok.Setter;
@ -11,6 +15,7 @@ public class TestCaseReportMetricDTO {
private List<TestCaseReportStatusResultDTO> executeResult;
private List<TestCaseReportModuleResultDTO> moduleExecuteResult;
private List<TestPlanCaseDTO> failureTestCases;
private List<String> executors;
private String principal;
private Long startTime;

View File

@ -25,4 +25,6 @@ public class QueryTestPlanCaseRequest extends TestPlanTestCase {
private String workspaceId;
private String name;
private String status;
}

View File

@ -2,11 +2,10 @@ package io.metersphere.track.service;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.TestCaseMapper;
import io.metersphere.base.mapper.TestCaseNodeMapper;
import io.metersphere.base.mapper.TestPlanMapper;
import io.metersphere.base.mapper.TestPlanTestCaseMapper;
import io.metersphere.base.mapper.*;
import io.metersphere.base.mapper.ext.ExtProjectMapper;
import io.metersphere.base.mapper.ext.ExtTestPlanMapper;
import io.metersphere.base.mapper.ext.ExtTestPlanTestCaseMapper;
@ -14,10 +13,14 @@ import io.metersphere.commons.constants.TestPlanStatus;
import io.metersphere.commons.constants.TestPlanTestCaseStatus;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.user.SessionUser;
import io.metersphere.commons.utils.MathUtils;
import io.metersphere.commons.utils.ServiceUtils;
import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.controller.request.ProjectRequest;
import io.metersphere.controller.request.member.QueryMemberRequest;
import io.metersphere.dto.ProjectDTO;
import io.metersphere.track.Factory.ReportComponentFactory;
import io.metersphere.track.domain.ReportComponent;
import io.metersphere.track.dto.*;
import io.metersphere.track.request.testcase.PlanCaseRelevanceRequest;
import io.metersphere.track.request.testcase.QueryTestPlanRequest;
@ -27,6 +30,7 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -58,15 +62,16 @@ public class TestPlanService {
@Resource
SqlSessionFactory sqlSessionFactory;
@Lazy
@Resource
TestCaseNodeMapper testCaseNodeMapper;
@Resource
TestCaseNodeService testCaseNodeService;
TestPlanTestCaseService testPlanTestCaseService;
@Resource
ExtProjectMapper extProjectMapper;
@Resource
TestCaseReportMapper testCaseReportMapper;
public void addTestPlan(TestPlan testPlan) {
if (getTestPlanByName(testPlan.getName()).size() > 0) {
MSException.throwException(Translator.get("plan_name_already_exists"));
@ -226,23 +231,17 @@ public class TestPlanService {
}
});
}
testPlan.setPassRate(getPercentWithTwoDecimals(testPlan.getTested() == 0 ? 0 : testPlan.getPassed()*1.0/testPlan.getTested()));
testPlan.setTestRate(getPercentWithTwoDecimals(testPlan.getTotal() == 0 ? 0 : testPlan.getTested()*1.0/testPlan.getTotal()));
testPlan.setPassRate(MathUtils.getPercentWithDecimal(testPlan.getTested() == 0 ? 0 : testPlan.getPassed()*1.0/testPlan.getTested()));
testPlan.setTestRate(MathUtils.getPercentWithDecimal(testPlan.getTotal() == 0 ? 0 : testPlan.getTested()*1.0/testPlan.getTotal()));
});
return testPlans;
}
private double getPercentWithTwoDecimals(double value) {
return new BigDecimal(value * 100)
.setScale(1, BigDecimal.ROUND_HALF_UP)
.doubleValue();
}
public List<TestPlanCaseDTO> listTestCaseByPlanId(String planId) {
QueryTestPlanCaseRequest request = new QueryTestPlanCaseRequest();
request.setPlanId(planId);
return extTestPlanTestCaseMapper.list(request);
return testPlanTestCaseService.list(request);
}
public List<TestPlanCaseDTO> listTestCaseByProjectIds(List<String> projectIds) {
@ -255,107 +254,26 @@ public class TestPlanService {
QueryTestPlanRequest queryTestPlanRequest = new QueryTestPlanRequest();
queryTestPlanRequest.setId(planId);
TestPlanDTO testPlan = extTestPlanMapper.list(queryTestPlanRequest).get(0);
TestCaseReport testCaseReport = testCaseReportMapper.selectByPrimaryKey(testPlan.getReportId());
JSONObject content = JSONObject.parseObject(testCaseReport.getContent());
JSONArray componentIds = content.getJSONArray("components");
Set<String> executors = new HashSet<>();
Map<String, TestCaseReportStatusResultDTO> reportStatusResultMap = new HashMap<>();
TestCaseNodeExample testCaseNodeExample = new TestCaseNodeExample();
testCaseNodeExample.createCriteria().andProjectIdEqualTo(testPlan.getProjectId());
List<TestCaseNode> nodes = testCaseNodeMapper.selectByExample(testCaseNodeExample);
List<TestCaseNodeDTO> nodeTrees = testCaseNodeService.getNodeTrees(nodes);
Map<String, Set<String>> childIdMap = new HashMap<>();
nodeTrees.forEach(item -> {
Set<String> childIds = new HashSet<>();
getChildIds(item, childIds);
childIdMap.put(item.getId(), childIds);
});
List<ReportComponent> components = ReportComponentFactory.createComponents(componentIds.toJavaList(String.class), testPlan);
List<TestPlanCaseDTO> testPlanTestCases = listTestCaseByPlanId(planId);
Map<String, TestCaseReportModuleResultDTO> moduleResultMap = new HashMap<>();
for (TestPlanCaseDTO testCase: testPlanTestCases) {
executors.add(testCase.getExecutor());
getStatusResultMap(reportStatusResultMap, testCase);
getModuleResultMap(childIdMap, moduleResultMap, testCase, nodeTrees);
}
nodeTrees.forEach(rootNode -> {
TestCaseReportModuleResultDTO moduleResult = moduleResultMap.get(rootNode.getId());
if (moduleResult != null) {
moduleResult.setModuleName(rootNode.getName());
}
});
for (TestCaseReportModuleResultDTO moduleResult : moduleResultMap.values()) {
moduleResult.setPassRate(getPercentWithTwoDecimals(moduleResult.getPassCount()*1.0f/moduleResult.getCaseCount()));
if (moduleResult.getCaseCount() <= 0) {
moduleResultMap.remove(moduleResult.getModuleId());
}
components.forEach(component -> {
component.readRecord(testCase);
});
}
TestCaseReportMetricDTO testCaseReportMetricDTO = new TestCaseReportMetricDTO();
testCaseReportMetricDTO.setProjectName(testPlan.getProjectName());
testCaseReportMetricDTO.setPrincipal(testPlan.getPrincipal());
testCaseReportMetricDTO.setExecutors(new ArrayList<>(executors));
testCaseReportMetricDTO.setExecuteResult(new ArrayList<>(reportStatusResultMap.values()));
testCaseReportMetricDTO.setModuleExecuteResult(new ArrayList<>(moduleResultMap.values()));
return testCaseReportMetricDTO;
}
private void getStatusResultMap(Map<String, TestCaseReportStatusResultDTO> reportStatusResultMap, TestPlanCaseDTO testCase) {
TestCaseReportStatusResultDTO statusResult = reportStatusResultMap.get(testCase.getStatus());
if (statusResult == null) {
statusResult = new TestCaseReportStatusResultDTO();
statusResult.setStatus(testCase.getStatus());
statusResult.setCount(0);
}
statusResult.setCount(statusResult.getCount() + 1);
reportStatusResultMap.put(testCase.getStatus(), statusResult);
}
private void getModuleResultMap(Map<String, Set<String>> childIdMap, Map<String, TestCaseReportModuleResultDTO> moduleResultMap, TestPlanCaseDTO testCase, List<TestCaseNodeDTO> nodeTrees) {
childIdMap.forEach((rootNodeId, childIds) -> {
if (childIds.contains(testCase.getNodeId())) {
TestCaseReportModuleResultDTO moduleResult = moduleResultMap.get(rootNodeId);
if (moduleResult == null) {
moduleResult = new TestCaseReportModuleResultDTO();
moduleResult.setCaseCount(0);
moduleResult.setPassCount(0);
moduleResult.setIssuesCount(0);
moduleResult.setModuleId(rootNodeId);
}
moduleResult.setCaseCount(moduleResult.getCaseCount() + 1);
if (StringUtils.equals(testCase.getStatus(), TestPlanTestCaseStatus.Pass.name())) {
moduleResult.setPassCount(moduleResult.getPassCount() + 1);
}
if (StringUtils.isNotBlank(testCase.getIssues())) {
if (JSON.parseObject(testCase.getIssues()).getBoolean("hasIssues")) {
moduleResult.setIssuesCount(moduleResult.getIssuesCount() + 1);
};
}
moduleResultMap.put(rootNodeId, moduleResult);
return;
}
components.forEach(component -> {
component.afterBuild(testCaseReportMetricDTO);
});
}
private void getChildIds(TestCaseNodeDTO rootNode, Set<String> childIds) {
childIds.add(rootNode.getId());
List<TestCaseNodeDTO> children = rootNode.getChildren();
if(children != null) {
Iterator<TestCaseNodeDTO> iterator = children.iterator();
while(iterator.hasNext()){
getChildIds(iterator.next(), childIds);
}
}
return testCaseReportMetricDTO;
}
public List<TestPlan> getTestPlanByIds(List<String> planIds) {

View File

@ -5,7 +5,8 @@
<el-container class="test-container" v-loading="result.loading">
<el-header>
<el-row type="flex" align="middle">
<el-input :disabled="isReadOnly" class="test-name" v-model="test.name" maxlength="60" :placeholder="$t('api_test.input_name')"
<el-input :disabled="isReadOnly" class="test-name" v-model="test.name" maxlength="60"
:placeholder="$t('api_test.input_name')"
show-word-limit>
<el-select :disabled="isReadOnly" class="test-project" v-model="test.projectId" slot="prepend"
:placeholder="$t('api_test.select_project')">
@ -17,7 +18,8 @@
{{$t('commons.save')}}
</el-button>
<el-button type="primary" plain v-if="!isShowRun" :disabled="isDisabled || isReadOnly" @click="saveRunTest">
<el-button type="primary" plain v-if="!isShowRun" :disabled="isDisabled || isReadOnly"
@click="saveRunTest">
{{$t('load_test.save_and_run')}}
</el-button>
@ -25,7 +27,8 @@
{{$t('api_test.run')}}
</el-button>
<el-button :disabled="isReadOnly" type="warning" plain @click="cancel">{{$t('commons.cancel')}}</el-button>
<el-button :disabled="isReadOnly" type="warning" plain @click="cancel">{{$t('commons.cancel')}}
</el-button>
<el-dropdown trigger="click" @command="handleCommand">
<el-button class="el-dropdown-link more" icon="el-icon-more" plain/>
@ -165,13 +168,7 @@
},
getOptions(url) {
let formData = new FormData();
let request = {
id: this.test.id,
projectId: this.test.projectId,
name: this.test.name,
scenarioDefinition: JSON.stringify(this.test.scenarioDefinition)
}
let requestJson = JSON.stringify(request);
let requestJson = JSON.stringify(this.test);
formData.append('request', new Blob([requestJson], {
type: "application/json"

View File

@ -143,7 +143,7 @@
});
},
handleCopy(test) {
this.result = this.$post("/api/copy", {id: test.id}, () => {
this.result = this.$post("/api/copy", {projectId: test.projectId, id: test.id, name: test.name}, () => {
this.$success(this.$t('commons.copy_success'));
this.search();
});

View File

@ -15,13 +15,13 @@
<script>
import {ResponseTime} from "../../model/ScenarioModel";
import {Duration} from "../../model/ScenarioModel";
export default {
name: "MsApiAssertionResponseTime",
name: "MsApiAssertionDuration",
props: {
duration: ResponseTime,
duration: Duration,
value: [Number, String],
edit: Boolean,
callback: Function,

View File

@ -6,14 +6,14 @@
size="small">
<el-option :label="$t('api_test.request.assertions.text')" :value="options.TEXT"/>
<el-option :label="$t('api_test.request.assertions.regex')" :value="options.REGEX"/>
<el-option :label="$t('api_test.request.assertions.response_time')" :value="options.RESPONSE_TIME"/>
<el-option :label="$t('api_test.request.assertions.response_time')" :value="options.DURATION"/>
</el-select>
</el-col>
<el-col :span="20">
<ms-api-assertion-text :is-read-only="isReadOnly" :list="assertions.regex" v-if="type === options.TEXT" :callback="after"/>
<ms-api-assertion-regex :is-read-only="isReadOnly" :list="assertions.regex" v-if="type === options.REGEX" :callback="after"/>
<ms-api-assertion-response-time :is-read-only="isReadOnly" v-model="time" :duration="assertions.duration"
v-if="type === options.RESPONSE_TIME" :callback="after"/>
<ms-api-assertion-duration :is-read-only="isReadOnly" v-model="time" :duration="assertions.duration"
v-if="type === options.DURATION" :callback="after"/>
</el-col>
</el-row>
@ -24,14 +24,14 @@
<script>
import MsApiAssertionText from "./ApiAssertionText";
import MsApiAssertionRegex from "./ApiAssertionRegex";
import MsApiAssertionResponseTime from "./ApiAssertionResponseTime";
import {ASSERTION_TYPE, Assertions, ResponseTime} from "../../model/ScenarioModel";
import MsApiAssertionDuration from "./ApiAssertionDuration";
import {ASSERTION_TYPE, Assertions} from "../../model/ScenarioModel";
import MsApiAssertionsEdit from "./ApiAssertionsEdit";
export default {
name: "MsApiAssertions",
components: {MsApiAssertionsEdit, MsApiAssertionResponseTime, MsApiAssertionRegex, MsApiAssertionText},
components: {MsApiAssertionsEdit, MsApiAssertionDuration, MsApiAssertionRegex, MsApiAssertionText},
props: {
assertions: Assertions,

View File

@ -13,7 +13,7 @@
<div>
{{$t("api_test.request.assertions.response_time")}}
</div>
<ms-api-assertion-response-time :is-read-only="isReadOnly" v-model="assertions.duration.value" :duration="assertions.duration" :edit="true"/>
<ms-api-assertion-duration :is-read-only="isReadOnly" v-model="assertions.duration.value" :duration="assertions.duration" :edit="true"/>
</div>
</div>
@ -21,13 +21,13 @@
<script>
import MsApiAssertionRegex from "./ApiAssertionRegex";
import MsApiAssertionResponseTime from "./ApiAssertionResponseTime";
import MsApiAssertionDuration from "./ApiAssertionDuration";
import {Assertions} from "../../model/ScenarioModel";
export default {
name: "MsApiAssertionsEdit",
components: {MsApiAssertionResponseTime, MsApiAssertionRegex},
components: {MsApiAssertionDuration, MsApiAssertionRegex},
props: {
assertions: Assertions,

View File

@ -40,7 +40,7 @@ export const BODY_TYPE = {
export const ASSERTION_TYPE = {
TEXT: "Text",
REGEX: "Regex",
RESPONSE_TIME: "Response Time"
DURATION: "Duration"
}
export const ASSERTION_REGEX_SUBJECT = {
@ -253,7 +253,7 @@ export class Assertions extends BaseConfig {
initOptions(options) {
options = options || {};
options.duration = new ResponseTime(options.duration);
options.duration = new Duration(options.duration);
return options;
}
}
@ -291,9 +291,9 @@ export class Regex extends AssertionType {
}
}
export class ResponseTime extends AssertionType {
export class Duration extends AssertionType {
constructor(options) {
super(ASSERTION_TYPE.RESPONSE_TIME);
super(ASSERTION_TYPE.DURATION);
this.value = undefined;
this.set(options);

View File

@ -61,7 +61,7 @@
<el-input v-model="form.name" autocomplete="off"/>
</el-form-item>
<el-form-item :label="$t('commons.description')" prop="description">
<el-input v-model="form.description" autocomplete="off"/>
<el-input v-model="form.description" autocomplete="off" type="textarea"/>
</el-form-item>
</el-form>
<template v-slot:footer>

View File

@ -0,0 +1,120 @@
<template>
<common-component :title="$t('test_track.plan_view.failure_case')">
<template>
<el-table
row-key="id"
:data="failureTestCases">
<el-table-column
prop="name"
:label="$t('commons.name')"
show-overflow-tooltip>
</el-table-column>
<el-table-column
prop="priority"
column-key="priority"
:label="$t('test_track.case.priority')">
<template v-slot:default="scope">
<priority-table-item :value="scope.row.priority" ref="priority"/>
</template>
</el-table-column>
<el-table-column
prop="type"
column-key="type"
:label="$t('test_track.case.type')"
show-overflow-tooltip>
<template v-slot:default="scope">
<type-table-item :value="scope.row.type"/>
</template>
</el-table-column>
<el-table-column
prop="method"
column-key="method"
:label="$t('test_track.case.method')"
show-overflow-tooltip>
<template v-slot:default="scope">
<method-table-item :value="scope.row.method"/>
</template>
</el-table-column>
<el-table-column
prop="nodePath"
:label="$t('test_track.case.module')"
show-overflow-tooltip>
</el-table-column>
<el-table-column
prop="executorName"
:label="$t('test_track.plan_view.executor')">
</el-table-column>
<el-table-column
prop="status"
column-key="status"
:label="$t('test_track.plan_view.execute_result')">
<template v-slot:default="scope">
<status-table-item :value="scope.row.status"/>
</template>
</el-table-column>
<el-table-column
prop="updateTime"
:label="$t('commons.update_time')"
show-overflow-tooltip>
<template v-slot:default="scope">
<span>{{ scope.row.updateTime | timestampFormatDate }}</span>
</template>
</el-table-column>
</el-table>
</template>
</common-component>
</template>
<script>
import CommonComponent from "./CommonComponent";
import PriorityTableItem from "../../../../../common/tableItems/planview/PriorityTableItem";
import TypeTableItem from "../../../../../common/tableItems/planview/TypeTableItem";
import MethodTableItem from "../../../../../common/tableItems/planview/MethodTableItem";
import StatusTableItem from "../../../../../common/tableItems/planview/StatusTableItem";
export default {
name: "FailureResultComponent",
components: {StatusTableItem, MethodTableItem, TypeTableItem, PriorityTableItem, CommonComponent},
props: {
failureTestCases: {
type: Array,
default() {
return [
{
name: 'testCase1',
priority: 'P1',
type: 'api',
method: 'auto',
nodePath: '/module1/module2',
executorName: "Tom",
status: "Failure",
updateTime: new Date(),
},
{
name: 'testCase2',
priority: 'P0',
type: 'functional',
method: 'manual',
nodePath: '/module1',
executorName: "Micheal",
status: "Failure",
updateTime: new Date(),
}
]
}
}
}
}
</script>
<style scoped>
</style>

View File

@ -6,6 +6,7 @@
<base-info-component :is-report="false" v-if="preview.id == 1"/>
<test-result-component v-if="preview.id == 2"/>
<test-result-chart-component v-if="preview.id == 3"/>
<failure-result-component v-if="preview.id == 4"/>
<rich-text-component :preview="preview" v-if="preview.type != 'system'"/>
</div>
@ -14,6 +15,7 @@
<base-info-component :report-info="metric" v-if="preview.id == 1"/>
<test-result-component :test-results="metric.moduleExecuteResult" v-if="preview.id == 2"/>
<test-result-chart-component :execute-result="metric.executeResult" v-if="preview.id == 3"/>
<failure-result-component :failure-test-cases="metric.failureTestCases" v-if="preview.id == 4"/>
<rich-text-component :is-report-view="isReportView" :preview="preview" v-if="preview.type != 'system'"/>
</div>
@ -25,9 +27,12 @@
import TestResultComponent from "./TestResultComponent";
import TestResultChartComponent from "./TestResultChartComponent";
import RichTextComponent from "./RichTextComponent";
import FailureResultComponent from "./FailureResultComponent";
export default {
name: "TemplateComponent",
components: {RichTextComponent, TestResultChartComponent, TestResultComponent, BaseInfoComponent},
components: {
FailureResultComponent,
RichTextComponent, TestResultChartComponent, TestResultComponent, BaseInfoComponent},
props: {
preview: {
type: Object

View File

@ -76,10 +76,11 @@
[1, { name: this.$t('test_track.plan_view.base_info'), id: 1 , type: 'system'}],
[2, { name: this.$t('test_track.plan_view.test_result'), id: 2 , type: 'system'}],
[3, { name: this.$t('test_track.plan_view.result_distribution'), id: 3 ,type: 'system'}],
[4, { name: this.$t('test_track.plan_view.custom_component'), id: 4 ,type: 'custom'}]
[4, { name: this.$t('test_track.plan_view.failure_case'), id: 4 ,type: 'system'}],
[5, { name: this.$t('test_track.plan_view.custom_component'), id: 5 ,type: 'custom'}]
]
),
components: [4],
components: [5],
previews: [],
template: {},
isReport: false
@ -107,13 +108,13 @@
}
this.template = {
name: '',
content: {
components: [1,2,3,4],
customComponent: new Map()
content: {
components: [1,2,3,4,5],
customComponent: new Map()
}
};
this.previews = [];
this.components = [4];
this.components = [5];
if (id) {
this.type = 'edit';
this.getTemplateById(id);

View File

@ -68,7 +68,8 @@
[1, { name: this.$t('test_track.plan_view.base_info'), id: 1 , type: 'system'}],
[2, { name: this.$t('test_track.plan_view.test_result'), id: 2 , type: 'system'}],
[3, { name: this.$t('test_track.plan_view.result_distribution'), id: 3 ,type: 'system'}],
[4, { name: this.$t('test_track.plan_view.custom_component'), id: 4 ,type: 'custom'}]
[4, { name: this.$t('test_track.plan_view.failure_case'), id: 4 ,type: 'system'}],
[5, { name: this.$t('test_track.plan_view.custom_component'), id: 5 ,type: 'custom'}]
]
),
isTestManagerOrTestUser: false
@ -165,6 +166,15 @@
getMetric() {
this.result = this.$get('/test/plan/get/metric/' + this.planId, response => {
this.metric = response.data;
if (!this.metric.failureTestCases) {
this.metric.failureTestCases = [];
}
if (!this.metric.executeResult) {
this.metric.executeResult = [];
}
if (!this.metric.moduleExecuteResult) {
this.metric.moduleExecuteResult = [];
}
if (this.report.startTime) {
this.metric.startTime = new Date(this.report.startTime);
}

View File

@ -517,6 +517,7 @@ export default {
create_template: "Create template",
report_template: "Report template",
test_detail: "Test detail",
failure_case: "Failure case",
}
},
test_resource_pool: {

View File

@ -516,6 +516,7 @@ export default {
create_template: "新建模版",
report_template: "测试报告模版",
test_detail: "测试详情",
failure_case: "失败用例",
}
},
test_resource_pool: {

View File

@ -515,6 +515,7 @@ export default {
create_template: "新建模版",
report_template: "測試報告模版",
test_detail: "測試詳情",
failure_case: "失敗用例",
}
},
test_resource_pool: {