测试用例导入

This commit is contained in:
chenjianxing 2020-04-15 16:58:04 +08:00
parent 41a9fcfcf2
commit 276961d97b
17 changed files with 923 additions and 214 deletions

View File

@ -36,10 +36,6 @@
<artifactId>spring-boot-starter-tomcat</artifactId> <artifactId>spring-boot-starter-tomcat</artifactId>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
</exclusion> </exclusion>
<exclusion>
<artifactId>hibernate-validator</artifactId>
<groupId>org.hibernate.validator</groupId>
</exclusion>
</exclusions> </exclusions>
</dependency> </dependency>
@ -133,6 +129,13 @@
<version>5.1</version> <version>5.1</version>
</dependency> </dependency>
<!-- easyexcel -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.1.7</version>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -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;
}
}

View File

@ -0,0 +1,5 @@
package io.metersphere.commons.constants;
public class TestCaseConstants {
public static final int MAX_NODE_DEPTH = 5;
}

View File

@ -6,14 +6,11 @@ import io.metersphere.base.domain.*;
import io.metersphere.commons.utils.PageUtils; import io.metersphere.commons.utils.PageUtils;
import io.metersphere.commons.utils.Pager; import io.metersphere.commons.utils.Pager;
import io.metersphere.controller.request.testcase.QueryTestCaseRequest; import io.metersphere.controller.request.testcase.QueryTestCaseRequest;
import io.metersphere.controller.request.testplan.QueryTestPlanRequest; import io.metersphere.excel.domain.ExcelResponse;
import io.metersphere.dto.LoadTestDTO;
import io.metersphere.dto.TestCaseNodeDTO;
import io.metersphere.dto.TestPlanCaseDTO;
import io.metersphere.service.TestCaseNodeService;
import io.metersphere.service.TestCaseService; import io.metersphere.service.TestCaseService;
import io.metersphere.user.SessionUtils; import io.metersphere.user.SessionUtils;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.List; import java.util.List;
@ -74,5 +71,10 @@ public class TestCaseController {
return testCaseService.deleteTestCase(testCaseId); return testCaseService.deleteTestCase(testCaseId);
} }
@PostMapping("/import/{projectId}")
public ExcelResponse testCaseImport(MultipartFile file, @PathVariable String projectId){
return testCaseService.testCaseImport(file, projectId);
}
} }

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -6,8 +6,11 @@ import io.metersphere.base.mapper.TestCaseMapper;
import io.metersphere.base.mapper.TestCaseNodeMapper; import io.metersphere.base.mapper.TestCaseNodeMapper;
import io.metersphere.base.mapper.TestPlanMapper; import io.metersphere.base.mapper.TestPlanMapper;
import io.metersphere.base.mapper.TestPlanTestCaseMapper; import io.metersphere.base.mapper.TestPlanTestCaseMapper;
import io.metersphere.commons.constants.TestCaseConstants;
import io.metersphere.commons.utils.BeanUtils; import io.metersphere.commons.utils.BeanUtils;
import io.metersphere.dto.TestCaseNodeDTO; import io.metersphere.dto.TestCaseNodeDTO;
import io.metersphere.exception.ExcelImportException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -30,8 +33,8 @@ public class TestCaseNodeService {
public int addNode(TestCaseNode node) { public int addNode(TestCaseNode node) {
if(node.getLevel() > 5){ if(node.getLevel() > TestCaseConstants.MAX_NODE_DEPTH){
throw new RuntimeException("模块树最大深度为5层!"); throw new RuntimeException("模块树最大深度为" + TestCaseConstants.MAX_NODE_DEPTH + "层!");
} }
node.setCreateTime(System.currentTimeMillis()); node.setCreateTime(System.currentTimeMillis());
node.setUpdateTime(System.currentTimeMillis()); node.setUpdateTime(System.currentTimeMillis());
@ -196,4 +199,124 @@ public class TestCaseNodeService {
TestPlan testPlan = testPlanMapper.selectByPrimaryKey(planId); TestPlan testPlan = testPlanMapper.selectByPrimaryKey(planId);
return getNodeTreeByProjectId(testPlan.getProjectId()); 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();
}
} }

View File

@ -1,26 +1,34 @@
package io.metersphere.service; package io.metersphere.service;
import com.alibaba.excel.EasyExcelFactory;
import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageHelper;
import io.metersphere.base.domain.*; import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.ProjectMapper; import io.metersphere.base.mapper.*;
import io.metersphere.base.mapper.TestCaseMapper;
import io.metersphere.base.mapper.TestPlanMapper;
import io.metersphere.base.mapper.TestPlanTestCaseMapper;
import io.metersphere.base.mapper.ext.ExtTestCaseMapper; import io.metersphere.base.mapper.ext.ExtTestCaseMapper;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.controller.request.testcase.QueryTestCaseRequest; 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.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.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.ArrayList; import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
@Service @Service
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@ -41,6 +49,15 @@ public class TestCaseService {
@Resource @Resource
ProjectMapper projectMapper; ProjectMapper projectMapper;
@Resource
SqlSessionFactory sqlSessionFactory;
@Resource
TestCaseNodeService testCaseNodeService;
@Resource
UserMapper userMapper;
public void addTestCase(TestCaseWithBLOBs testCase) { public void addTestCase(TestCaseWithBLOBs testCase) {
testCase.setId(UUID.randomUUID().toString()); testCase.setId(UUID.randomUUID().toString());
testCase.setCreateTime(System.currentTimeMillis()); testCase.setCreateTime(System.currentTimeMillis());
@ -144,4 +161,59 @@ public class TestCaseService {
} }
return projectMapper.selectByPrimaryKey(testCaseWithBLOBs.getProjectId()); 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();
}
} }

View File

@ -26,6 +26,7 @@
:current-project="currentProject" :current-project="currentProject"
@openTestCaseEditDialog="openTestCaseEditDialog" @openTestCaseEditDialog="openTestCaseEditDialog"
@testCaseEdit="openTestCaseEditDialog" @testCaseEdit="openTestCaseEditDialog"
@refresh="refresh"
ref="testCaseList"> ref="testCaseList">
</test-case-list> </test-case-list>
</el-main> </el-main>

View File

@ -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>

View File

@ -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>

View File

@ -4,12 +4,21 @@
<el-card v-loading="result.loading"> <el-card v-loading="result.loading">
<template v-slot:header> <template v-slot:header>
<div> <div>
<el-row type="flex" justify="space-between" align="middle"> <el-row type="flex" justify="start" align="middle">
<el-col :span="5"> <el-col :span="5">
<span class="title">{{$t('test_track.test_case')}}</span> <span class="title">{{$t('test_track.test_case')}}</span>
<ms-create-box :tips="$t('test_track.create')" :exec="testCaseCreate"/> <ms-create-box :tips="$t('test_track.create')" :exec="testCaseCreate"/>
</el-col> </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"> <el-col :span="5">
<span class="search"> <span class="search">
<el-input type="text" size="small" :placeholder="$t('load_test.search_by_name')" <el-input type="text" size="small" :placeholder="$t('load_test.search_by_name')"
@ -105,10 +114,12 @@
<script> <script>
import MsCreateBox from '../../../settings/CreateBox'; import MsCreateBox from '../../../settings/CreateBox';
import TestCaseImport from '../components/TestCaseImport';
import TestCaseExport from '../components/TestCaseExport';
export default { export default {
name: "TestCaseList", name: "TestCaseList",
components: {MsCreateBox}, components: {MsCreateBox, TestCaseImport, TestCaseExport},
data() { data() {
return { return {
result: {}, result: {},
@ -195,6 +206,9 @@
type: 'success' type: 'success'
}); });
}); });
},
refresh() {
this.$emit('refresh');
} }
} }
} }