feat(测试跟踪):通过Excel导入导出时有ID字段,可通过Excel导入来更新用例。 (#1727)

Co-authored-by: 黎龙鑫 <lilongxinya@163.com>
This commit is contained in:
Ambitiousliga 2021-03-29 09:35:12 +08:00 committed by chenjianxing
parent 9c96088006
commit c1d7f5df42
9 changed files with 232 additions and 15 deletions

View File

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

View File

@ -9,5 +9,7 @@ public class ExcelResponse<T> {
private Boolean success;
private List<ExcelErrData<T>> errList;
private Boolean isUpdated; //是否有更新过用例
}

View File

@ -8,6 +8,8 @@ import lombok.Setter;
@Setter
public class TestCaseExcelData {
@ExcelIgnore
private Integer num;
@ExcelIgnore
private String name;
@ExcelIgnore

View File

@ -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("用例名称")

View File

@ -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("用例名稱")

View File

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

View File

@ -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);
}
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();
}
}
@ -137,6 +190,32 @@ public class TestCaseDataListener extends EasyExcelListener<TestCaseExcelData> {
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);
return testCase;
}
public String getSteps(TestCaseExcelData data) {
JSONArray jsonArray = new JSONArray();
@ -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();
}
}
}

View File

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

View File

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