feat(测试跟踪):通过Excel导入导出时有ID字段,可通过Excel导入来更新用例。 (#1727)
Co-authored-by: 黎龙鑫 <lilongxinya@163.com>
This commit is contained in:
parent
9c96088006
commit
c1d7f5df42
|
@ -1,17 +1,18 @@
|
|||
package io.metersphere.base.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class TestCaseWithBLOBs extends TestCase implements Serializable {
|
||||
private String remark;
|
||||
|
||||
private String steps;
|
||||
private String steps; //与TestCaseExcelData里的属性名不一致,BeanUtils.copyBean()复制不了值,需要手动赋值
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
|
@ -9,5 +9,7 @@ public class ExcelResponse<T> {
|
|||
|
||||
private Boolean success;
|
||||
private List<ExcelErrData<T>> errList;
|
||||
private Boolean isUpdated; //是否有更新过用例
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@ import lombok.Setter;
|
|||
@Setter
|
||||
public class TestCaseExcelData {
|
||||
|
||||
@ExcelIgnore
|
||||
private Integer num;
|
||||
@ExcelIgnore
|
||||
private String name;
|
||||
@ExcelIgnore
|
||||
|
|
|
@ -13,6 +13,10 @@ import javax.validation.constraints.Pattern;
|
|||
@ColumnWidth(15)
|
||||
public class TestCaseExcelDataCn extends TestCaseExcelData {
|
||||
|
||||
@ExcelProperty("ID")
|
||||
@NotRequired
|
||||
private Integer num;
|
||||
|
||||
@NotBlank(message = "{cannot_be_null}")
|
||||
@Length(max = 255)
|
||||
@ExcelProperty("用例名称")
|
||||
|
|
|
@ -13,6 +13,10 @@ import javax.validation.constraints.Pattern;
|
|||
@ColumnWidth(15)
|
||||
public class TestCaseExcelDataTw extends TestCaseExcelData {
|
||||
|
||||
@ExcelProperty("ID")
|
||||
@NotRequired
|
||||
private Integer num;
|
||||
|
||||
@NotBlank(message = "{cannot_be_null}")
|
||||
@Length(max = 255)
|
||||
@ExcelProperty("用例名稱")
|
||||
|
|
|
@ -14,6 +14,10 @@ import javax.validation.constraints.Pattern;
|
|||
@ColumnWidth(15)
|
||||
public class TestCaseExcelDataUs extends TestCaseExcelData {
|
||||
|
||||
@ExcelProperty("ID")
|
||||
@NotRequired
|
||||
private Integer num;
|
||||
|
||||
@NotBlank(message = "{cannot_be_null}")
|
||||
@Length(max = 255)
|
||||
@ExcelProperty("Name")
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
package io.metersphere.excel.listener;
|
||||
|
||||
import com.alibaba.excel.context.AnalysisContext;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import io.metersphere.base.domain.TestCaseWithBLOBs;
|
||||
import io.metersphere.commons.constants.TestCaseConstants;
|
||||
import io.metersphere.commons.utils.BeanUtils;
|
||||
import io.metersphere.commons.utils.CommonBeanFactory;
|
||||
import io.metersphere.commons.utils.LogUtil;
|
||||
import io.metersphere.excel.domain.ExcelErrData;
|
||||
import io.metersphere.excel.domain.TestCaseExcelData;
|
||||
import io.metersphere.excel.utils.ExcelValidateHelper;
|
||||
import io.metersphere.i18n.Translator;
|
||||
import io.metersphere.track.service.TestCaseService;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
@ -22,10 +26,18 @@ public class TestCaseDataListener extends EasyExcelListener<TestCaseExcelData> {
|
|||
|
||||
private String projectId;
|
||||
|
||||
protected List<TestCaseExcelData> updateList = new ArrayList<>(); //存储待更新用例的集合
|
||||
|
||||
protected boolean isUpdated = false; //判断是否更新过用例,将会传给前端
|
||||
|
||||
Set<String> testCaseNames;
|
||||
|
||||
Set<String> userIds;
|
||||
|
||||
public boolean isUpdated() {
|
||||
return isUpdated;
|
||||
}
|
||||
|
||||
public TestCaseDataListener(Class clazz, String projectId, Set<String> testCaseNames, Set<String> userIds) {
|
||||
this.clazz = clazz;
|
||||
this.testCaseService = (TestCaseService) CommonBeanFactory.getBean("testCaseService");
|
||||
|
@ -39,12 +51,15 @@ public class TestCaseDataListener extends EasyExcelListener<TestCaseExcelData> {
|
|||
String nodePath = data.getNodePath();
|
||||
StringBuilder stringBuilder = new StringBuilder(errMsg);
|
||||
|
||||
//校验”所属模块"
|
||||
if (nodePath != null) {
|
||||
String[] nodes = nodePath.split("/");
|
||||
//校验模块深度
|
||||
if (nodes.length > TestCaseConstants.MAX_NODE_DEPTH + 1) {
|
||||
stringBuilder.append(Translator.get("test_case_node_level_tip") +
|
||||
TestCaseConstants.MAX_NODE_DEPTH + Translator.get("test_case_node_level") + "; ");
|
||||
}
|
||||
//模块名不能为空
|
||||
for (int i = 0; i < nodes.length; i++) {
|
||||
if (i != 0 && StringUtils.equals(nodes[i].trim(), "")) {
|
||||
stringBuilder.append(Translator.get("module_not_null") + "; ");
|
||||
|
@ -57,10 +72,39 @@ public class TestCaseDataListener extends EasyExcelListener<TestCaseExcelData> {
|
|||
// stringBuilder.append(Translator.get("functional_method_tip") + "; ");
|
||||
// }
|
||||
|
||||
|
||||
//校验维护人
|
||||
if (!userIds.contains(data.getMaintainer())) {
|
||||
stringBuilder.append(Translator.get("user_not_exists") + ":" + data.getMaintainer() + "; ");
|
||||
}
|
||||
|
||||
/*
|
||||
校验Excel中是否有ID
|
||||
有的话校验ID是否已在当前项目中存在,存在则更新用例,
|
||||
不存在则继续校验看是否重复,不重复则新建用例。
|
||||
*/
|
||||
if (null != data.getNum()) { //当前读取的数据有ID
|
||||
|
||||
if (null != testCaseService.checkIdExist(data.getNum(), projectId)) { //该ID在当前项目中存在
|
||||
//如果前面所经过的校验都没报错
|
||||
if (StringUtils.isEmpty(stringBuilder)) {
|
||||
updateList.add(data); //将当前数据存入更新列表
|
||||
stringBuilder.append("update_testcase"); //该信息用于在invoke方法中判断是否该更新用例
|
||||
}
|
||||
return stringBuilder.toString();
|
||||
} else {
|
||||
/*
|
||||
该ID在当前数据库中不存在,应当继续校验用例是否重复,
|
||||
在下面的校验过程中,num的值会被用于判断是否重复,所以应当先设置为null
|
||||
*/
|
||||
data.setNum(null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
校验用例
|
||||
*/
|
||||
if (testCaseNames.contains(data.getName())) {
|
||||
TestCaseWithBLOBs testCase = new TestCaseWithBLOBs();
|
||||
BeanUtils.copyBean(testCase, data);
|
||||
|
@ -96,18 +140,27 @@ public class TestCaseDataListener extends EasyExcelListener<TestCaseExcelData> {
|
|||
@Override
|
||||
public void saveData() {
|
||||
|
||||
//无错误数据才插入数据
|
||||
if (!errList.isEmpty()) {
|
||||
//excel中用例都有错误时就返回,只要有用例可用于更新或者插入就不返回
|
||||
if (!errList.isEmpty() && list.size() == 0 && updateList.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Collections.reverse(list);
|
||||
if (!(list.size() == 0)){
|
||||
Collections.reverse(list); //因为saveImportData里面是先分配最大的ID,这个ID应该先发给list中最后的数据,所以要reverse
|
||||
List<TestCaseWithBLOBs> result = list.stream()
|
||||
.map(item -> this.convert2TestCase(item))
|
||||
.collect(Collectors.toList());
|
||||
testCaseService.saveImportData(result, projectId);
|
||||
}
|
||||
|
||||
List<TestCaseWithBLOBs> result = list.stream()
|
||||
.map(item -> this.convert2TestCase(item))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
testCaseService.saveImportData(result, projectId);
|
||||
if (!(updateList.size() == 0)) {
|
||||
List<TestCaseWithBLOBs> result2 = updateList.stream()
|
||||
.map(item -> this.convert2TestCaseForUpdate(item))
|
||||
.collect(Collectors.toList());
|
||||
testCaseService.updateImportDataCarryId(result2, projectId);
|
||||
this.isUpdated = true;
|
||||
updateList.clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -131,6 +184,32 @@ public class TestCaseDataListener extends EasyExcelListener<TestCaseExcelData> {
|
|||
testCase.setNodePath(nodePath);
|
||||
|
||||
|
||||
String steps = getSteps(data);
|
||||
testCase.setSteps(steps);
|
||||
|
||||
return testCase;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将Excel中的数据对象转换为用于更新操作的用例数据对象,
|
||||
* @param data
|
||||
* @return
|
||||
*/
|
||||
private TestCaseWithBLOBs convert2TestCaseForUpdate(TestCaseExcelData data) {
|
||||
TestCaseWithBLOBs testCase = new TestCaseWithBLOBs();
|
||||
BeanUtils.copyBean(testCase, data);
|
||||
testCase.setProjectId(this.projectId);
|
||||
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);
|
||||
|
||||
String steps = getSteps(data);
|
||||
testCase.setSteps(steps);
|
||||
|
||||
|
@ -189,4 +268,38 @@ public class TestCaseDataListener extends EasyExcelListener<TestCaseExcelData> {
|
|||
return jsonArray.toJSONString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invoke(TestCaseExcelData testCaseExcelData, AnalysisContext analysisContext) {
|
||||
String errMsg;
|
||||
Integer rowIndex = analysisContext.readRowHolder().getRowIndex();
|
||||
String updateMsg = "update_testcase";
|
||||
try {
|
||||
//根据excel数据实体中的javax.validation + 正则表达式来校验excel数据
|
||||
errMsg = ExcelValidateHelper.validateEntity(testCaseExcelData);
|
||||
//自定义校验规则
|
||||
errMsg = validate(testCaseExcelData, errMsg);
|
||||
} catch (NoSuchFieldException e) {
|
||||
errMsg = Translator.get("parse_data_error");
|
||||
LogUtil.error(e.getMessage(), e);
|
||||
}
|
||||
|
||||
if (!StringUtils.isEmpty(errMsg)) {
|
||||
|
||||
//如果errMsg只有"update testcase",说明用例待更新
|
||||
if (!errMsg.equals(updateMsg)){
|
||||
ExcelErrData excelErrData = new ExcelErrData(testCaseExcelData, rowIndex,
|
||||
Translator.get("number") + " " + rowIndex + " " + Translator.get("row") + Translator.get("error")
|
||||
+ ":" + errMsg);
|
||||
|
||||
errList.add(excelErrData);
|
||||
}
|
||||
} else {
|
||||
list.add(testCaseExcelData);
|
||||
}
|
||||
|
||||
if (list.size() > BATCH_COUNT) {
|
||||
saveData();
|
||||
list.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ import io.metersphere.excel.domain.ExcelErrData;
|
|||
import io.metersphere.excel.domain.ExcelResponse;
|
||||
import io.metersphere.excel.domain.TestCaseExcelData;
|
||||
import io.metersphere.excel.domain.TestCaseExcelDataFactory;
|
||||
import io.metersphere.excel.listener.EasyExcelListener;
|
||||
import io.metersphere.excel.listener.TestCaseDataListener;
|
||||
import io.metersphere.excel.utils.EasyExcelExporter;
|
||||
import io.metersphere.i18n.Translator;
|
||||
|
@ -126,11 +125,26 @@ public class TestCaseService {
|
|||
|
||||
// 全部字段值相同才判断为用例存在
|
||||
if (testCase != null) {
|
||||
|
||||
/*
|
||||
例如对于“/模块5”,用户的输入可能为“模块5”或者“/模块5/”或者“模块5/”。
|
||||
不这样处理的话,下面进行判断时就会用用户输入的错误格式进行判断,而模块名为“/模块5”、
|
||||
“模块5”、“/模块5/”、“模块5/”时,它们应该被认为是同一个模块。
|
||||
数据库存储的node_path都是“/模块5”这种格式的
|
||||
*/
|
||||
String nodePath = testCase.getNodePath();
|
||||
if (!nodePath.startsWith("/")) {
|
||||
nodePath = "/" + nodePath;
|
||||
}
|
||||
if (nodePath.endsWith("/")) {
|
||||
nodePath = nodePath.substring(0, nodePath.length() - 1);
|
||||
}
|
||||
|
||||
TestCaseExample example = new TestCaseExample();
|
||||
TestCaseExample.Criteria criteria = example.createCriteria();
|
||||
criteria.andNameEqualTo(testCase.getName())
|
||||
.andProjectIdEqualTo(testCase.getProjectId())
|
||||
.andNodePathEqualTo(testCase.getNodePath())
|
||||
.andNodePathEqualTo(nodePath)
|
||||
.andTypeEqualTo(testCase.getType())
|
||||
.andMaintainerEqualTo(testCase.getMaintainer())
|
||||
.andPriorityEqualTo(testCase.getPriority());
|
||||
|
@ -177,6 +191,26 @@ public class TestCaseService {
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id和pojectId查询id是否在数据库中存在。
|
||||
* 在数据库中单id的话是可重复的,id与projectId的组合是唯一的
|
||||
*/
|
||||
public Integer checkIdExist(Integer id, String projectId){
|
||||
TestCaseExample example = new TestCaseExample();
|
||||
TestCaseExample.Criteria criteria = example.createCriteria();
|
||||
if (null != id) {
|
||||
criteria.andNumEqualTo(id);
|
||||
criteria.andProjectIdEqualTo(projectId);
|
||||
long count = testCaseMapper.countByExample(example); //查询是否有包含此ID的数据
|
||||
if(count == 0){ //如果ID不存在
|
||||
return null;
|
||||
}else { //有对应ID的数据
|
||||
return id;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public int deleteTestCase(String testCaseId) {
|
||||
TestPlanTestCaseExample example = new TestPlanTestCaseExample();
|
||||
example.createCriteria().andCaseIdEqualTo(testCaseId);
|
||||
|
@ -286,6 +320,7 @@ public class TestCaseService {
|
|||
public ExcelResponse testCaseImport(MultipartFile multipartFile, String projectId, String userId) {
|
||||
|
||||
ExcelResponse excelResponse = new ExcelResponse();
|
||||
boolean isUpdated = false; //判断是否更新了用例
|
||||
String currentWorkspaceId = SessionUtils.getCurrentWorkspaceId();
|
||||
QueryTestCaseRequest queryTestCaseRequest = new QueryTestCaseRequest();
|
||||
queryTestCaseRequest.setProjectId(projectId);
|
||||
|
@ -338,10 +373,15 @@ public class TestCaseService {
|
|||
Set<String> userIds = userRoleMapper.selectByExample(userRoleExample).stream().map(UserRole::getUserId).collect(Collectors.toSet());
|
||||
|
||||
try {
|
||||
//根据本地语言环境选择用哪种数据对象进行存放读取的数据
|
||||
Class clazz = new TestCaseExcelDataFactory().getExcelDataByLocal();
|
||||
EasyExcelListener easyExcelListener = new TestCaseDataListener(clazz, projectId, testCaseNames, userIds);
|
||||
|
||||
TestCaseDataListener easyExcelListener = new TestCaseDataListener(clazz, projectId, testCaseNames, userIds);
|
||||
//读取excel数据
|
||||
EasyExcelFactory.read(multipartFile.getInputStream(), clazz, easyExcelListener).sheet().doRead();
|
||||
|
||||
errList = easyExcelListener.getErrList();
|
||||
isUpdated = easyExcelListener.isUpdated();
|
||||
} catch (Exception e) {
|
||||
LogUtil.error(e.getMessage(), e);
|
||||
MSException.throwException(e.getMessage());
|
||||
|
@ -352,6 +392,7 @@ public class TestCaseService {
|
|||
if (!errList.isEmpty()) {
|
||||
excelResponse.setSuccess(false);
|
||||
excelResponse.setErrList(errList);
|
||||
excelResponse.setIsUpdated(isUpdated);
|
||||
} else {
|
||||
excelResponse.setSuccess(true);
|
||||
}
|
||||
|
@ -374,7 +415,7 @@ public class TestCaseService {
|
|||
testcase.setSort(sort.getAndIncrement());
|
||||
testcase.setNum(num.decrementAndGet());
|
||||
testcase.setReviewStatus(TestCaseReviewStatus.Prepare.name());
|
||||
mapper.insert(testcase);
|
||||
mapper.insert(testcase);
|
||||
});
|
||||
}
|
||||
sqlSession.flushStatements();
|
||||
|
@ -400,6 +441,43 @@ public class TestCaseService {
|
|||
sqlSession.flushStatements();
|
||||
}
|
||||
|
||||
/**
|
||||
* 把Excel中带ID的数据更新到数据库
|
||||
* @param testCases
|
||||
* @param projectId
|
||||
*/
|
||||
public void updateImportDataCarryId(List<TestCaseWithBLOBs> testCases, String projectId) {
|
||||
Map<String, String> nodePathMap = testCaseNodeService.createNodeByTestCases(testCases, projectId);
|
||||
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
|
||||
TestCaseMapper mapper = sqlSession.getMapper(TestCaseMapper.class);
|
||||
|
||||
/*
|
||||
获取用例的“网页上所显示id”与“数据库ID”映射。
|
||||
*/
|
||||
List<Integer> nums = testCases.stream()
|
||||
.map(TestCase::getNum)
|
||||
.collect(Collectors.toList());
|
||||
TestCaseExample example = new TestCaseExample();
|
||||
example.createCriteria().andNumIn(nums)
|
||||
.andProjectIdEqualTo(projectId);
|
||||
List<TestCase> testCasesList = testCaseMapper.selectByExample(example);
|
||||
Map<Integer, String> numIdMap = testCasesList.stream()
|
||||
.collect(Collectors.toMap(TestCase::getNum, TestCase::getId));
|
||||
|
||||
|
||||
if (!testCases.isEmpty()) {
|
||||
AtomicInteger sort = new AtomicInteger();
|
||||
testCases.forEach(testcase -> {
|
||||
testcase.setUpdateTime(System.currentTimeMillis());
|
||||
testcase.setNodeId(nodePathMap.get(testcase.getNodePath()));
|
||||
testcase.setSort(sort.getAndIncrement());
|
||||
testcase.setId(numIdMap.get(testcase.getNum()));
|
||||
mapper.updateByPrimaryKeySelective(testcase);
|
||||
});
|
||||
}
|
||||
sqlSession.flushStatements();
|
||||
}
|
||||
|
||||
|
||||
public void testCaseTemplateExport(HttpServletResponse response) {
|
||||
try {
|
||||
|
@ -510,6 +588,7 @@ public class TestCaseService {
|
|||
StringBuilder result = new StringBuilder("");
|
||||
TestCaseList.forEach(t -> {
|
||||
TestCaseExcelData data = new TestCaseExcelData();
|
||||
data.setNum(t.getNum());
|
||||
data.setName(t.getName());
|
||||
data.setNodePath(t.getNodePath());
|
||||
data.setPriority(t.getPriority());
|
||||
|
|
|
@ -126,7 +126,8 @@
|
|||
fileList: [],
|
||||
errList: [],
|
||||
xmindErrList: [],
|
||||
isLoading: false
|
||||
isLoading: false,
|
||||
isUpdated: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -180,6 +181,12 @@
|
|||
this.fileList = [];
|
||||
this.errList = [];
|
||||
this.xmindErrList = [];
|
||||
|
||||
//通过excel导入更新过数据的话就刷新页面
|
||||
if (this.isUpdated === true){
|
||||
this.$emit("refreshAll");
|
||||
this.isUpdated = false;
|
||||
}
|
||||
},
|
||||
downloadTemplate() {
|
||||
this.$fileDownload('/test/case/export/template');
|
||||
|
@ -207,6 +214,7 @@
|
|||
this.$emit("refreshAll");
|
||||
} else {
|
||||
this.errList = res.errList;
|
||||
this.isUpdated = res.isUpdated;
|
||||
}
|
||||
this.fileList = [];
|
||||
}, erro => {
|
||||
|
|
Loading…
Reference in New Issue