测试用例导入
This commit is contained in:
parent
41a9fcfcf2
commit
276961d97b
|
@ -36,10 +36,6 @@
|
|||
<artifactId>spring-boot-starter-tomcat</artifactId>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<artifactId>hibernate-validator</artifactId>
|
||||
<groupId>org.hibernate.validator</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
|
@ -133,6 +129,13 @@
|
|||
<version>5.1</version>
|
||||
</dependency>
|
||||
|
||||
<!-- easyexcel -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>easyexcel</artifactId>
|
||||
<version>2.1.7</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
|
|
@ -1,194 +0,0 @@
|
|||
package io.metersphere.base.domain;
|
||||
|
||||
public class ZaleniumTest {
|
||||
|
||||
private String seleniumSessionId;
|
||||
private String testName;
|
||||
private String timestamp;
|
||||
private String addedToDashboardTime;
|
||||
private String browser;
|
||||
private String browserVersion;
|
||||
private String proxyName;
|
||||
private String platform;
|
||||
private String fileName;
|
||||
private String fileExtension;
|
||||
private String videoFolderPath;
|
||||
private String logsFolderPath;
|
||||
private String testNameNoExtension;
|
||||
private String screenDimension;
|
||||
private String timeZone;
|
||||
private String build;
|
||||
private String testFileNameTemplate;
|
||||
private String browserDriverLogFileName;
|
||||
private String retentionDate;
|
||||
private String testStatus;
|
||||
private boolean videoRecorded;
|
||||
|
||||
public String getSeleniumSessionId() {
|
||||
return seleniumSessionId;
|
||||
}
|
||||
|
||||
public void setSeleniumSessionId(String seleniumSessionId) {
|
||||
this.seleniumSessionId = seleniumSessionId;
|
||||
}
|
||||
|
||||
public String getTestName() {
|
||||
return testName;
|
||||
}
|
||||
|
||||
public void setTestName(String testName) {
|
||||
this.testName = testName;
|
||||
}
|
||||
|
||||
public String getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public void setTimestamp(String timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getAddedToDashboardTime() {
|
||||
return addedToDashboardTime;
|
||||
}
|
||||
|
||||
public void setAddedToDashboardTime(String addedToDashboardTime) {
|
||||
this.addedToDashboardTime = addedToDashboardTime;
|
||||
}
|
||||
|
||||
public String getBrowser() {
|
||||
return browser;
|
||||
}
|
||||
|
||||
public void setBrowser(String browser) {
|
||||
this.browser = browser;
|
||||
}
|
||||
|
||||
public String getBrowserVersion() {
|
||||
return browserVersion;
|
||||
}
|
||||
|
||||
public void setBrowserVersion(String browserVersion) {
|
||||
this.browserVersion = browserVersion;
|
||||
}
|
||||
|
||||
public String getProxyName() {
|
||||
return proxyName;
|
||||
}
|
||||
|
||||
public void setProxyName(String proxyName) {
|
||||
this.proxyName = proxyName;
|
||||
}
|
||||
|
||||
public String getPlatform() {
|
||||
return platform;
|
||||
}
|
||||
|
||||
public void setPlatform(String platform) {
|
||||
this.platform = platform;
|
||||
}
|
||||
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public void setFileName(String fileName) {
|
||||
this.fileName = fileName;
|
||||
}
|
||||
|
||||
public String getFileExtension() {
|
||||
return fileExtension;
|
||||
}
|
||||
|
||||
public void setFileExtension(String fileExtension) {
|
||||
this.fileExtension = fileExtension;
|
||||
}
|
||||
|
||||
public String getVideoFolderPath() {
|
||||
return videoFolderPath;
|
||||
}
|
||||
|
||||
public void setVideoFolderPath(String videoFolderPath) {
|
||||
this.videoFolderPath = videoFolderPath;
|
||||
}
|
||||
|
||||
public String getLogsFolderPath() {
|
||||
return logsFolderPath;
|
||||
}
|
||||
|
||||
public void setLogsFolderPath(String logsFolderPath) {
|
||||
this.logsFolderPath = logsFolderPath;
|
||||
}
|
||||
|
||||
public String getTestNameNoExtension() {
|
||||
return testNameNoExtension;
|
||||
}
|
||||
|
||||
public void setTestNameNoExtension(String testNameNoExtension) {
|
||||
this.testNameNoExtension = testNameNoExtension;
|
||||
}
|
||||
|
||||
public String getScreenDimension() {
|
||||
return screenDimension;
|
||||
}
|
||||
|
||||
public void setScreenDimension(String screenDimension) {
|
||||
this.screenDimension = screenDimension;
|
||||
}
|
||||
|
||||
public String getTimeZone() {
|
||||
return timeZone;
|
||||
}
|
||||
|
||||
public void setTimeZone(String timeZone) {
|
||||
this.timeZone = timeZone;
|
||||
}
|
||||
|
||||
public String getBuild() {
|
||||
return build;
|
||||
}
|
||||
|
||||
public void setBuild(String build) {
|
||||
this.build = build;
|
||||
}
|
||||
|
||||
public String getTestFileNameTemplate() {
|
||||
return testFileNameTemplate;
|
||||
}
|
||||
|
||||
public void setTestFileNameTemplate(String testFileNameTemplate) {
|
||||
this.testFileNameTemplate = testFileNameTemplate;
|
||||
}
|
||||
|
||||
public String getBrowserDriverLogFileName() {
|
||||
return browserDriverLogFileName;
|
||||
}
|
||||
|
||||
public void setBrowserDriverLogFileName(String browserDriverLogFileName) {
|
||||
this.browserDriverLogFileName = browserDriverLogFileName;
|
||||
}
|
||||
|
||||
public String getRetentionDate() {
|
||||
return retentionDate;
|
||||
}
|
||||
|
||||
public void setRetentionDate(String retentionDate) {
|
||||
this.retentionDate = retentionDate;
|
||||
}
|
||||
|
||||
public String getTestStatus() {
|
||||
return testStatus;
|
||||
}
|
||||
|
||||
public void setTestStatus(String testStatus) {
|
||||
this.testStatus = testStatus;
|
||||
}
|
||||
|
||||
public boolean isVideoRecorded() {
|
||||
return videoRecorded;
|
||||
}
|
||||
|
||||
public void setVideoRecorded(boolean videoRecorded) {
|
||||
this.videoRecorded = videoRecorded;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package io.metersphere.commons.constants;
|
||||
|
||||
public class TestCaseConstants {
|
||||
public static final int MAX_NODE_DEPTH = 5;
|
||||
}
|
|
@ -6,14 +6,11 @@ import io.metersphere.base.domain.*;
|
|||
import io.metersphere.commons.utils.PageUtils;
|
||||
import io.metersphere.commons.utils.Pager;
|
||||
import io.metersphere.controller.request.testcase.QueryTestCaseRequest;
|
||||
import io.metersphere.controller.request.testplan.QueryTestPlanRequest;
|
||||
import io.metersphere.dto.LoadTestDTO;
|
||||
import io.metersphere.dto.TestCaseNodeDTO;
|
||||
import io.metersphere.dto.TestPlanCaseDTO;
|
||||
import io.metersphere.service.TestCaseNodeService;
|
||||
import io.metersphere.excel.domain.ExcelResponse;
|
||||
import io.metersphere.service.TestCaseService;
|
||||
import io.metersphere.user.SessionUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
|
@ -74,5 +71,10 @@ public class TestCaseController {
|
|||
return testCaseService.deleteTestCase(testCaseId);
|
||||
}
|
||||
|
||||
@PostMapping("/import/{projectId}")
|
||||
public ExcelResponse testCaseImport(MultipartFile file, @PathVariable String projectId){
|
||||
return testCaseService.testCaseImport(file, projectId);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
package io.metersphere.excel.domain;
|
||||
|
||||
public class ExcelErrData<T> {
|
||||
|
||||
private T t;
|
||||
|
||||
private Integer rowNum;
|
||||
|
||||
private String errMsg;
|
||||
|
||||
public ExcelErrData(){}
|
||||
|
||||
public ExcelErrData(T t, Integer rowNum,String errMsg){
|
||||
this.t = t;
|
||||
this.rowNum = rowNum;
|
||||
this.errMsg = errMsg;
|
||||
}
|
||||
|
||||
public T getT() {
|
||||
return t;
|
||||
}
|
||||
|
||||
public void setT(T t) {
|
||||
this.t = t;
|
||||
}
|
||||
|
||||
public String getErrMsg() {
|
||||
return errMsg;
|
||||
}
|
||||
|
||||
public void setErrMsg(String errMsg) {
|
||||
this.errMsg = errMsg;
|
||||
}
|
||||
|
||||
public Integer getRowNum() {
|
||||
return rowNum;
|
||||
}
|
||||
|
||||
public void setRowNum(Integer rowNum) {
|
||||
this.rowNum = rowNum;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package io.metersphere.excel.domain;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ExcelResponse<T> {
|
||||
|
||||
private Boolean success;
|
||||
private List<ExcelErrData<T>> errList;
|
||||
|
||||
public Boolean getSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
public void setSuccess(Boolean success) {
|
||||
this.success = success;
|
||||
}
|
||||
|
||||
public List<ExcelErrData<T>> getErrList() {
|
||||
return errList;
|
||||
}
|
||||
|
||||
public void setErrList(List<ExcelErrData<T>> errList) {
|
||||
this.errList = errList;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
package io.metersphere.excel.domain;
|
||||
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.Pattern;
|
||||
|
||||
public class TestCaseExcelData {
|
||||
|
||||
@NotBlank
|
||||
@Length(max=1000)
|
||||
@ExcelProperty("所属模块")
|
||||
@Pattern(regexp = "^(?!.*//).*$", message = "格式不正确")
|
||||
private String nodePath;
|
||||
|
||||
@NotBlank
|
||||
@Length(max=50)
|
||||
@ExcelProperty("用例名称")
|
||||
private String name;
|
||||
|
||||
@NotBlank
|
||||
@ExcelProperty("用例类型")
|
||||
@Pattern(regexp = "(^functional$)|(^performance$)|(^api$)", message = "必须为functional、performance、api")
|
||||
private String type;
|
||||
|
||||
@NotBlank
|
||||
@ExcelProperty("维护人")
|
||||
private String maintainer;
|
||||
|
||||
@NotBlank
|
||||
@ExcelProperty("优先级")
|
||||
@Pattern(regexp = "(^P0$)|(^P1$)|(^P2$)|(^P3$)", message = "必须为P0、P1、P2、P3")
|
||||
private String priority;
|
||||
|
||||
@NotBlank
|
||||
@ExcelProperty("测试方式")
|
||||
@Pattern(regexp = "(^manual$)|(^auto$)", message = "必须为manual、auto")
|
||||
private String method;
|
||||
|
||||
@ExcelProperty("前置条件")
|
||||
@Length(min=0, max=1000)
|
||||
private String prerequisite;
|
||||
|
||||
@ExcelProperty("备注")
|
||||
@Length(max=1000)
|
||||
private String remark;
|
||||
|
||||
@ExcelProperty("步骤描述")
|
||||
@Length(max=1000)
|
||||
private String stepDesc;
|
||||
|
||||
@ExcelProperty("预期结果")
|
||||
@Length(max=1000)
|
||||
private String stepResult;
|
||||
|
||||
public String getNodePath() {
|
||||
return nodePath;
|
||||
}
|
||||
|
||||
public void setNodePath(String nodePath) {
|
||||
this.nodePath = nodePath;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getMaintainer() {
|
||||
return maintainer;
|
||||
}
|
||||
|
||||
public void setMaintainer(String maintainer) {
|
||||
this.maintainer = maintainer;
|
||||
}
|
||||
|
||||
public String getPriority() {
|
||||
return priority;
|
||||
}
|
||||
|
||||
public void setPriority(String priority) {
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
public String getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
public void setMethod(String method) {
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
public String getPrerequisite() {
|
||||
return prerequisite;
|
||||
}
|
||||
|
||||
public void setPrerequisite(String prerequisite) {
|
||||
this.prerequisite = prerequisite;
|
||||
}
|
||||
|
||||
public String getRemark() {
|
||||
return remark;
|
||||
}
|
||||
|
||||
public void setRemark(String remark) {
|
||||
this.remark = remark;
|
||||
}
|
||||
|
||||
public String getStepDesc() {
|
||||
return stepDesc;
|
||||
}
|
||||
|
||||
public void setStepDesc(String stepDesc) {
|
||||
this.stepDesc = stepDesc;
|
||||
}
|
||||
|
||||
public String getStepResult() {
|
||||
return stepResult;
|
||||
}
|
||||
|
||||
public void setStepResult(String stepResult) {
|
||||
this.stepResult = stepResult;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
package io.metersphere.excel.listener;
|
||||
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
import com.alibaba.excel.context.AnalysisContext;
|
||||
import com.alibaba.excel.event.AnalysisEventListener;
|
||||
import com.alibaba.excel.exception.ExcelAnalysisException;
|
||||
import com.alibaba.excel.util.StringUtils;
|
||||
import io.metersphere.commons.utils.LogUtil;
|
||||
import io.metersphere.excel.util.ExcelValidateHelper;
|
||||
import io.metersphere.excel.domain.ExcelErrData;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
|
||||
|
||||
public abstract class EasyExcelListener <T> extends AnalysisEventListener<T> {
|
||||
|
||||
protected List<ExcelErrData<T>> errList = new ArrayList<>();
|
||||
|
||||
protected List<T> list = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 每隔2000条存储数据库,然后清理list ,方便内存回收
|
||||
*/
|
||||
protected static final int BATCH_COUNT = 2000;
|
||||
|
||||
protected Class<T> clazz;
|
||||
|
||||
|
||||
public EasyExcelListener(Class<T> clazz){
|
||||
this.clazz = clazz;
|
||||
}
|
||||
|
||||
/**
|
||||
* 这个每一条数据解析都会来调用
|
||||
*
|
||||
* @param t
|
||||
* @param analysisContext
|
||||
*/
|
||||
@Override
|
||||
public void invoke(T t, AnalysisContext analysisContext) {
|
||||
String errMsg;
|
||||
Integer rowIndex = analysisContext.readRowHolder().getRowIndex();
|
||||
try {
|
||||
//根据excel数据实体中的javax.validation + 正则表达式来校验excel数据
|
||||
errMsg = ExcelValidateHelper.validateEntity(t);
|
||||
//自定义校验规则
|
||||
errMsg = validate(t, errMsg);
|
||||
} catch (NoSuchFieldException e) {
|
||||
errMsg = "解析数据出错";
|
||||
LogUtil.error(e.getMessage(), e);
|
||||
}
|
||||
|
||||
if (!StringUtils.isEmpty(errMsg)) {
|
||||
ExcelErrData excelErrData = new ExcelErrData(t, rowIndex, "第" + rowIndex + "行出错:" + errMsg);
|
||||
errList.add(excelErrData);
|
||||
} else {
|
||||
list.add(t);
|
||||
}
|
||||
|
||||
if (list.size() > BATCH_COUNT) {
|
||||
saveData();
|
||||
list.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 可重写该方法
|
||||
* 自定义校验规则
|
||||
* @param data
|
||||
* @param errMsg
|
||||
* @return
|
||||
*/
|
||||
public String validate(T data, String errMsg) {
|
||||
return errMsg;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义数据保存操作
|
||||
*/
|
||||
public abstract void saveData();
|
||||
|
||||
@Override
|
||||
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
|
||||
saveData();
|
||||
list.clear();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 校验excel头部
|
||||
* @param headMap 传入excel的头部(第一行数据)数据的index,name
|
||||
* @param context
|
||||
*/
|
||||
@Override
|
||||
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
|
||||
super.invokeHeadMap(headMap, context);
|
||||
if (clazz != null){
|
||||
try {
|
||||
Set<String> fieldNameSet = getFieldNameSet(clazz);
|
||||
Collection<String> values = headMap.values();
|
||||
for (String key : fieldNameSet) {
|
||||
if (!values.contains(key)){
|
||||
throw new ExcelAnalysisException("缺少头部信息:" + key);
|
||||
}
|
||||
}
|
||||
} catch (NoSuchFieldException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 获取注解里ExcelProperty的value
|
||||
*/
|
||||
public Set<String> getFieldNameSet(Class clazz) throws NoSuchFieldException {
|
||||
Set<String> result = new HashSet<>();
|
||||
Field field;
|
||||
Field[] fields = clazz.getDeclaredFields();
|
||||
for (int i = 0; i < fields.length ; i++) {
|
||||
field = clazz.getDeclaredField(fields[i].getName());
|
||||
field.setAccessible(true);
|
||||
ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
|
||||
if(excelProperty != null){
|
||||
StringBuilder value = new StringBuilder();
|
||||
for (String v : excelProperty.value()) {
|
||||
value.append(v);
|
||||
}
|
||||
result.add(value.toString());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public List<ExcelErrData<T>> getErrList() {
|
||||
return errList;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
package io.metersphere.excel.listener;
|
||||
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import io.metersphere.excel.domain.TestCaseExcelData;
|
||||
import io.metersphere.base.domain.TestCaseWithBLOBs;
|
||||
import io.metersphere.commons.constants.TestCaseConstants;
|
||||
import io.metersphere.commons.utils.BeanUtils;
|
||||
import io.metersphere.service.TestCaseService;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class TestCaseDataListener extends EasyExcelListener<TestCaseExcelData> {
|
||||
|
||||
private TestCaseService testCaseService;
|
||||
|
||||
private String projectId;
|
||||
|
||||
Set<String> testCaseNames;
|
||||
|
||||
Set<String> userNames;
|
||||
|
||||
public TestCaseDataListener(TestCaseService testCaseService, String projectId,
|
||||
Set<String> testCaseNames, Set<String> userNames, Class<TestCaseExcelData> clazz) {
|
||||
super(clazz);
|
||||
this.testCaseService = testCaseService;
|
||||
this.projectId = projectId;
|
||||
this.testCaseNames = testCaseNames;
|
||||
this.userNames = userNames;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String validate(TestCaseExcelData data, String errMsg) {
|
||||
String nodePath = data.getNodePath();
|
||||
StringBuilder stringBuilder = new StringBuilder(errMsg);
|
||||
if ( nodePath.split("/").length > TestCaseConstants.MAX_NODE_DEPTH + 1) {
|
||||
stringBuilder.append("节点最多为" + TestCaseConstants.MAX_NODE_DEPTH + "层;");
|
||||
}
|
||||
if (!userNames.contains(data.getMaintainer())) {
|
||||
stringBuilder.append("该工作空间下无该用户:" + data.getMaintainer() + ";");
|
||||
}
|
||||
if (testCaseNames.contains(data.getName())) {
|
||||
stringBuilder.append("该项目下已存在该测试用例:" + data.getName() + ";");
|
||||
}
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveData() {
|
||||
|
||||
//无错误数据才插入数据
|
||||
if (!errList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<TestCaseWithBLOBs> result = list.stream()
|
||||
.map(item -> this.convert2TestCase(item))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
testCaseService.saveImportData(result, projectId);
|
||||
|
||||
}
|
||||
|
||||
|
||||
private TestCaseWithBLOBs convert2TestCase(TestCaseExcelData data) {
|
||||
TestCaseWithBLOBs testCase = new TestCaseWithBLOBs();
|
||||
BeanUtils.copyBean(testCase, data);
|
||||
testCase.setId(UUID.randomUUID().toString());
|
||||
testCase.setProjectId(this.projectId);
|
||||
testCase.setCreateTime(System.currentTimeMillis());
|
||||
testCase.setUpdateTime(System.currentTimeMillis());
|
||||
String nodePath = data.getNodePath();
|
||||
|
||||
if (!nodePath.startsWith("/")) {
|
||||
nodePath = "/" + nodePath;
|
||||
}
|
||||
if (nodePath.endsWith("/")) {
|
||||
nodePath = nodePath.substring(0, nodePath.length() - 1);
|
||||
}
|
||||
|
||||
testCase.setNodePath(nodePath);
|
||||
|
||||
|
||||
JSONArray jsonArray = new JSONArray();
|
||||
|
||||
String[] stepDesc = new String[0];
|
||||
String[] stepRes = new String[0];
|
||||
|
||||
if (data.getStepDesc() != null) {
|
||||
stepDesc = data.getStepDesc().split("\n");
|
||||
}
|
||||
if (data.getStepResult() != null) {
|
||||
stepRes = data.getStepResult().split("\n");
|
||||
}
|
||||
|
||||
String pattern = "(^\\d+)(\\.)?";
|
||||
int index = stepDesc.length > stepRes.length ? stepDesc.length : stepRes.length;
|
||||
|
||||
for (int i = 0; i < index; i++){
|
||||
|
||||
JSONObject step = new JSONObject();
|
||||
step.put("num", i + 1);
|
||||
|
||||
Pattern descPattern = Pattern.compile(pattern);
|
||||
Pattern resPattern = Pattern.compile(pattern);
|
||||
|
||||
if (i < stepDesc.length) {
|
||||
Matcher descMatcher = descPattern.matcher(stepDesc[i]);
|
||||
if (descMatcher.find()) {
|
||||
step.put("desc", descMatcher.replaceAll(""));
|
||||
} else {
|
||||
step.put("desc", stepDesc[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (i < stepRes.length) {
|
||||
Matcher resMatcher = resPattern.matcher(stepRes[i]);
|
||||
if (resMatcher.find()) {
|
||||
step.put("result", resMatcher.replaceAll(""));
|
||||
} else {
|
||||
step.put("result", stepRes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
jsonArray.add(step);
|
||||
}
|
||||
|
||||
testCase.setSteps(jsonArray.toJSONString());
|
||||
|
||||
return testCase;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package io.metersphere.excel.util;
|
||||
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.Validation;
|
||||
import javax.validation.Validator;
|
||||
import javax.validation.groups.Default;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
public class ExcelValidateHelper {
|
||||
|
||||
private ExcelValidateHelper(){}
|
||||
|
||||
private static Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
|
||||
|
||||
public static <T> String validateEntity(T obj) throws NoSuchFieldException {
|
||||
StringBuilder result = new StringBuilder();
|
||||
Set<ConstraintViolation<T>> set = validator.validate(obj, Default.class);
|
||||
if (set != null && !set.isEmpty()) {
|
||||
for (ConstraintViolation<T> cv : set) {
|
||||
Field declaredField = obj.getClass().getDeclaredField(cv.getPropertyPath().toString());
|
||||
ExcelProperty annotation = declaredField.getAnnotation(ExcelProperty.class);
|
||||
//拼接错误信息,包含当前出错数据的标题名字+错误信息
|
||||
result.append(annotation.value()[0]+cv.getMessage()).append(";");
|
||||
}
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package io.metersphere.exception;
|
||||
|
||||
/**
|
||||
* @author jianxing.chen
|
||||
*/
|
||||
public class ExcelImportException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public ExcelImportException(String message, Exception e){
|
||||
super(message, e);
|
||||
}
|
||||
|
||||
public ExcelImportException(String message){
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
|
@ -6,8 +6,11 @@ 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.commons.constants.TestCaseConstants;
|
||||
import io.metersphere.commons.utils.BeanUtils;
|
||||
import io.metersphere.dto.TestCaseNodeDTO;
|
||||
import io.metersphere.exception.ExcelImportException;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
@ -30,8 +33,8 @@ public class TestCaseNodeService {
|
|||
|
||||
public int addNode(TestCaseNode node) {
|
||||
|
||||
if(node.getLevel() > 5){
|
||||
throw new RuntimeException("模块树最大深度为5层!");
|
||||
if(node.getLevel() > TestCaseConstants.MAX_NODE_DEPTH){
|
||||
throw new RuntimeException("模块树最大深度为" + TestCaseConstants.MAX_NODE_DEPTH + "层!");
|
||||
}
|
||||
node.setCreateTime(System.currentTimeMillis());
|
||||
node.setUpdateTime(System.currentTimeMillis());
|
||||
|
@ -196,4 +199,124 @@ public class TestCaseNodeService {
|
|||
TestPlan testPlan = testPlanMapper.selectByPrimaryKey(planId);
|
||||
return getNodeTreeByProjectId(testPlan.getProjectId());
|
||||
}
|
||||
|
||||
public Map<String, Integer> createNodeByTestCases(List<TestCaseWithBLOBs> testCases, String projectId) {
|
||||
|
||||
List<TestCaseNodeDTO> nodeTrees = getNodeTreeByProjectId(projectId);
|
||||
|
||||
Map<String, Integer> pathMap = new HashMap<>();
|
||||
|
||||
List<String> nodePaths = testCases.stream()
|
||||
.map(TestCase::getNodePath)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
nodePaths.forEach(path -> {
|
||||
|
||||
if (path == null) {
|
||||
throw new ExcelImportException("所属模块不能为空!");
|
||||
}
|
||||
List<String> nodeNameList = new ArrayList<>(Arrays.asList(path.split("/")));
|
||||
Iterator<String> pathIterator = nodeNameList.iterator();
|
||||
|
||||
Boolean hasNode = false;
|
||||
String rootNodeName = null;
|
||||
|
||||
if (nodeNameList.size() <= 1) {
|
||||
throw new ExcelImportException("创建模块失败:" + path);
|
||||
} else {
|
||||
pathIterator.next();
|
||||
pathIterator.remove();
|
||||
|
||||
rootNodeName = pathIterator.next().trim();
|
||||
for (TestCaseNodeDTO nodeTree : nodeTrees) {
|
||||
if (StringUtils.equals(rootNodeName, nodeTree.getName())) {
|
||||
hasNode = true;
|
||||
createNodeByPathIterator(pathIterator, "/" + rootNodeName, nodeTree,
|
||||
pathMap, projectId, 2);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasNode) {
|
||||
createNodeByPath(pathIterator, rootNodeName, null, projectId, 1, "", pathMap);
|
||||
}
|
||||
});
|
||||
|
||||
return pathMap;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据目标节点路径,创建相关节点
|
||||
* @param pathIterator 遍历子路径
|
||||
* @param path 当前路径
|
||||
* @param treeNode 当前节点
|
||||
* @param pathMap 记录节点路径对应的nodeId
|
||||
*/
|
||||
private void createNodeByPathIterator(Iterator<String> pathIterator, String path, TestCaseNodeDTO treeNode,
|
||||
Map<String, Integer> pathMap, String projectId, Integer level) {
|
||||
|
||||
List<TestCaseNodeDTO> children = treeNode.getChildren();
|
||||
|
||||
if (children == null || children.isEmpty() || !pathIterator.hasNext()) {
|
||||
pathMap.put(path , treeNode.getId());
|
||||
if (pathIterator.hasNext()) {
|
||||
createNodeByPath(pathIterator, pathIterator.next().trim(), treeNode, projectId, level, path, pathMap);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
String nodeName = pathIterator.next().trim();
|
||||
|
||||
Boolean hasNode = false;
|
||||
|
||||
for (TestCaseNodeDTO child : children) {
|
||||
if (StringUtils.equals(nodeName, child.getName())) {
|
||||
hasNode = true;
|
||||
createNodeByPathIterator(pathIterator, path + "/" + child.getName(),
|
||||
child, pathMap, projectId, level + 1);
|
||||
};
|
||||
}
|
||||
|
||||
//若子节点中不包含该目标节点,则在该节点下创建
|
||||
if (!hasNode) {
|
||||
createNodeByPath(pathIterator, nodeName, treeNode, projectId, level, path, pathMap);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param pathIterator 迭代器,遍历子节点
|
||||
* @param nodeName 当前节点
|
||||
* @param pNode 父节点
|
||||
*/
|
||||
private void createNodeByPath(Iterator<String> pathIterator, String nodeName,
|
||||
TestCaseNodeDTO pNode, String projectId, Integer level,
|
||||
String rootPath, Map<String, Integer> pathMap) {
|
||||
|
||||
StringBuilder path = new StringBuilder(rootPath);
|
||||
|
||||
Integer pid = insertTestCaseNode(nodeName, pNode == null ? null : pNode.getId(), projectId, level);
|
||||
path.append("/" + nodeName);
|
||||
pathMap.put(path.toString(), pid);
|
||||
while (pathIterator.hasNext()) {
|
||||
String nextNodeName = pathIterator.next();
|
||||
path.append("/" + nextNodeName);
|
||||
pid = insertTestCaseNode(nextNodeName, pid, projectId, ++level);
|
||||
pathMap.put(path.toString(), pid);
|
||||
}
|
||||
}
|
||||
|
||||
private Integer insertTestCaseNode(String nodName, Integer pId, String projectId, Integer level) {
|
||||
TestCaseNode testCaseNode = new TestCaseNode();
|
||||
testCaseNode.setName(nodName.trim());
|
||||
testCaseNode.setpId(pId);
|
||||
testCaseNode.setProjectId(projectId);
|
||||
testCaseNode.setCreateTime(System.currentTimeMillis());
|
||||
testCaseNode.setUpdateTime(System.currentTimeMillis());
|
||||
testCaseNode.setLevel(level);
|
||||
testCaseNodeMapper.insert(testCaseNode);
|
||||
return testCaseNode.getId();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,26 +1,34 @@
|
|||
package io.metersphere.service;
|
||||
|
||||
|
||||
import com.alibaba.excel.EasyExcelFactory;
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import io.metersphere.base.domain.*;
|
||||
import io.metersphere.base.mapper.ProjectMapper;
|
||||
import io.metersphere.base.mapper.TestCaseMapper;
|
||||
import io.metersphere.base.mapper.TestPlanMapper;
|
||||
import io.metersphere.base.mapper.TestPlanTestCaseMapper;
|
||||
import io.metersphere.base.mapper.*;
|
||||
import io.metersphere.base.mapper.ext.ExtTestCaseMapper;
|
||||
import io.metersphere.commons.utils.LogUtil;
|
||||
import io.metersphere.controller.request.testcase.QueryTestCaseRequest;
|
||||
import io.metersphere.dto.TestPlanCaseDTO;
|
||||
import io.metersphere.excel.domain.ExcelErrData;
|
||||
import io.metersphere.excel.domain.ExcelResponse;
|
||||
import io.metersphere.excel.domain.TestCaseExcelData;
|
||||
import io.metersphere.excel.listener.EasyExcelListener;
|
||||
import io.metersphere.excel.listener.TestCaseDataListener;
|
||||
import io.metersphere.user.SessionUtils;
|
||||
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.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.ArrayList;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Service
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
|
@ -41,6 +49,15 @@ public class TestCaseService {
|
|||
@Resource
|
||||
ProjectMapper projectMapper;
|
||||
|
||||
@Resource
|
||||
SqlSessionFactory sqlSessionFactory;
|
||||
|
||||
@Resource
|
||||
TestCaseNodeService testCaseNodeService;
|
||||
|
||||
@Resource
|
||||
UserMapper userMapper;
|
||||
|
||||
public void addTestCase(TestCaseWithBLOBs testCase) {
|
||||
testCase.setId(UUID.randomUUID().toString());
|
||||
testCase.setCreateTime(System.currentTimeMillis());
|
||||
|
@ -144,4 +161,59 @@ public class TestCaseService {
|
|||
}
|
||||
return projectMapper.selectByPrimaryKey(testCaseWithBLOBs.getProjectId());
|
||||
}
|
||||
|
||||
public ExcelResponse testCaseImport(MultipartFile file, String projectId) {
|
||||
|
||||
try {
|
||||
|
||||
ExcelResponse excelResponse = new ExcelResponse();
|
||||
|
||||
String currentWorkspaceId = SessionUtils.getCurrentWorkspaceId();
|
||||
QueryTestCaseRequest queryTestCaseRequest = new QueryTestCaseRequest();
|
||||
queryTestCaseRequest.setProjectId(projectId);
|
||||
List<TestCase> testCases = extTestCaseMapper.getTestCaseNames(queryTestCaseRequest);
|
||||
Set<String> testCaseNames = testCases.stream()
|
||||
.map(TestCase::getName)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
UserExample userExample = new UserExample();
|
||||
userExample.createCriteria().andLastWorkspaceIdEqualTo(currentWorkspaceId);
|
||||
List<User> users = userMapper.selectByExample(userExample);
|
||||
Set<String> userNames = users.stream().map(User::getName).collect(Collectors.toSet());
|
||||
|
||||
EasyExcelListener easyExcelListener = new TestCaseDataListener(this, projectId,
|
||||
testCaseNames, userNames, TestCaseExcelData.class);
|
||||
EasyExcelFactory.read(file.getInputStream(), TestCaseExcelData.class, easyExcelListener).sheet().doRead();
|
||||
|
||||
List<ExcelErrData<TestCaseExcelData>> errList = easyExcelListener.getErrList();
|
||||
//如果包含错误信息就导出错误信息
|
||||
if (!errList.isEmpty()) {
|
||||
excelResponse.setSuccess(false);
|
||||
excelResponse.setErrList(errList);
|
||||
} else {
|
||||
excelResponse.setSuccess(true);
|
||||
}
|
||||
return excelResponse;
|
||||
|
||||
} catch (IOException e) {
|
||||
LogUtil.error(e.getMessage(), e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void saveImportData(List<TestCaseWithBLOBs> testCases, String projectId) {
|
||||
|
||||
Map<String, Integer> nodePathMap = testCaseNodeService.createNodeByTestCases(testCases, projectId);
|
||||
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
|
||||
TestCaseMapper mapper = sqlSession.getMapper(TestCaseMapper.class);
|
||||
if (!testCases.isEmpty()) {
|
||||
testCases.forEach(testcase -> {
|
||||
testcase.setNodeId(nodePathMap.get(testcase.getNodePath()));
|
||||
mapper.insert(testcase);
|
||||
});
|
||||
}
|
||||
sqlSession.flushStatements();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
:current-project="currentProject"
|
||||
@openTestCaseEditDialog="openTestCaseEditDialog"
|
||||
@testCaseEdit="openTestCaseEditDialog"
|
||||
@refresh="refresh"
|
||||
ref="testCaseList">
|
||||
</test-case-list>
|
||||
</el-main>
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-tooltip class="item" effect="dark" content="导出用例" placement="right">
|
||||
<el-button type="info" icon="el-icon-download" size="mini" circle></el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "TestCaseImport"
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,134 @@
|
|||
<template>
|
||||
|
||||
<div>
|
||||
<el-tooltip class="item" effect="dark" content="导入用例" placement="right">
|
||||
<el-button type="info" icon="el-icon-upload2" size="mini" circle
|
||||
@click="dialogVisible = true"></el-button>
|
||||
</el-tooltip>
|
||||
|
||||
<el-dialog width="30%" title="导入测试用例" :visible.sync="dialogVisible"
|
||||
@close="init">
|
||||
|
||||
<el-row>
|
||||
<el-link type="primary" class="download-template">下载模版</el-link>
|
||||
</el-row>
|
||||
|
||||
<el-row>
|
||||
<el-upload
|
||||
class="upload-demo"
|
||||
:action="'/test/case/import/' + projectId"
|
||||
:on-preview="handlePreview"
|
||||
multiple
|
||||
:limit="1"
|
||||
:on-exceed="handleExceed"
|
||||
:beforeUpload="UploadValidate"
|
||||
:on-success="handleSuccess"
|
||||
:on-error="handleError"
|
||||
:file-list="fileList">
|
||||
<template v-slot:trigger>
|
||||
<el-button size="mini" type="success" plain>点击上传</el-button>
|
||||
</template>
|
||||
<template v-slot:tip>
|
||||
<div class="el-upload__tip">只能上传xls/xlsx文件,且不超过20M</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
|
||||
</el-row>
|
||||
|
||||
<el-row>
|
||||
<ul>
|
||||
<li v-for="errFile in errList" :key="errFile.rowNum">
|
||||
{{errFile.errMsg}}
|
||||
</li>
|
||||
</ul>
|
||||
</el-row>
|
||||
|
||||
</el-dialog>
|
||||
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ElUploadList from "element-ui/packages/upload/src/upload-list";
|
||||
export default {
|
||||
name: "TestCaseImport",
|
||||
components: {ElUploadList},
|
||||
data() {
|
||||
return {
|
||||
dialogVisible: false,
|
||||
fileList: [],
|
||||
errList: []
|
||||
}
|
||||
},
|
||||
props: {
|
||||
projectId: {
|
||||
type: String
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handlePreview(file) {
|
||||
console.log("init");
|
||||
this.init();
|
||||
},
|
||||
handleExceed(files, fileList) {
|
||||
this.$message.warning(`当前限制选择 1 个文件,本次选择了 ${files.length} 个文件`);
|
||||
},
|
||||
UploadValidate(file) {
|
||||
var suffix =file.name.substring(file.name.lastIndexOf('.') + 1);
|
||||
if (suffix != 'xls' && suffix != 'xlsx') {
|
||||
this.$message({
|
||||
message: '上传文件只能是 xls、xlsx格式!',
|
||||
type: 'warning'
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
if (file.size / 1024 / 1024 > 20) {
|
||||
this.$message({
|
||||
message: '上传文件大小不能超过 20MB!',
|
||||
type: 'warning'
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
handleSuccess(response) {
|
||||
let res = response.data;
|
||||
if (res.success) {
|
||||
this.$message.success("导入成功!");
|
||||
this.dialogVisible = false;
|
||||
this.$emit("refresh");
|
||||
} else {
|
||||
this.errList = res.errList;
|
||||
}
|
||||
this.fileList = [];
|
||||
},
|
||||
handleError(err, file, fileList) {
|
||||
this.$message.error(err.message);
|
||||
},
|
||||
init() {
|
||||
this.fileList = [];
|
||||
this.errList = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
.el-dialog__body {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.download-template {
|
||||
padding-top: 0px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<style scoped>
|
||||
|
||||
|
||||
</style>
|
|
@ -4,12 +4,21 @@
|
|||
<el-card v-loading="result.loading">
|
||||
<template v-slot:header>
|
||||
<div>
|
||||
<el-row type="flex" justify="space-between" align="middle">
|
||||
<el-row type="flex" justify="start" align="middle">
|
||||
<el-col :span="5">
|
||||
<span class="title">{{$t('test_track.test_case')}}</span>
|
||||
<ms-create-box :tips="$t('test_track.create')" :exec="testCaseCreate"/>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="1" :offset="12">
|
||||
<test-case-import :projectId="currentProject == null? null : currentProject.id"
|
||||
@refresh="refresh"/>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="1">
|
||||
<test-case-export/>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="5">
|
||||
<span class="search">
|
||||
<el-input type="text" size="small" :placeholder="$t('load_test.search_by_name')"
|
||||
|
@ -105,10 +114,12 @@
|
|||
<script>
|
||||
|
||||
import MsCreateBox from '../../../settings/CreateBox';
|
||||
import TestCaseImport from '../components/TestCaseImport';
|
||||
import TestCaseExport from '../components/TestCaseExport';
|
||||
|
||||
export default {
|
||||
name: "TestCaseList",
|
||||
components: {MsCreateBox},
|
||||
components: {MsCreateBox, TestCaseImport, TestCaseExport},
|
||||
data() {
|
||||
return {
|
||||
result: {},
|
||||
|
@ -195,6 +206,9 @@
|
|||
type: 'success'
|
||||
});
|
||||
});
|
||||
},
|
||||
refresh() {
|
||||
this.$emit('refresh');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue