测试计划关联测试用例

This commit is contained in:
chenjianxing 2020-04-06 17:54:11 +08:00
parent d421965476
commit 83fe299506
23 changed files with 323 additions and 97 deletions

View File

@ -4,6 +4,7 @@ import io.metersphere.base.domain.TestCase;
import io.metersphere.controller.request.ReportRequest;
import io.metersphere.controller.request.testcase.QueryTestCaseRequest;
import io.metersphere.dto.ReportDTO;
import io.metersphere.dto.TestPlanCaseDTO;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@ -11,4 +12,6 @@ import java.util.List;
public interface ExtTestCaseMapper {
List<TestCase> getTestCaseNames(@Param("request") QueryTestCaseRequest request);
List<TestPlanCaseDTO> getTestPlanTestCases(@Param("request") QueryTestCaseRequest request);
}

View File

@ -20,4 +20,22 @@
ORDER BY test_case.update_time DESC
</select>
<select id="getTestPlanTestCases" resultType="io.metersphere.dto.TestPlanCaseDTO">
select t1.id as id, t1.plan_id as planId, t1.executor as executor, t1.status as status, t1.results as results, t1.remark remard, t1.create_time as createTime, t1.update_time updateTime,
t2.id as caseId, t2.node_id as nodeId, t2.node_path as nodePath, t2.project_id as projectId, t2.name as name,
t2.type as type, t2.maintainer as maintainer, t2.priority as priority, t2.method as method, t2.prerequisite as prerequisite
from test_plan_test_case as t1
inner join test_case as t2 on
t1.plan_id = #{request.planId}
<if test="request.nodeIds != null and request.nodeIds.size() > 0">
and t2.node_id in
<foreach collection="request.nodeIds" open="(" close=")" separator="," item="nodeId">
#{nodeId}
</foreach>
</if>
and t1.case_id = t2.id
<if test="request.name != null">
and t2.name like CONCAT('%', #{request.name},'%')
</if>
</select>
</mapper>

View File

@ -0,0 +1,5 @@
package io.metersphere.commons.constants;
public enum TestPlanTestCaseStatus {
Prepare, Pass, Failure, Blocking, Skip
}

View File

@ -12,6 +12,7 @@ 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.service.TestCaseService;
import io.metersphere.user.SessionUtils;
@ -38,6 +39,12 @@ public class TestCaseController {
return testCaseService.getTestCaseByNodeId(nodeIds);
}
@PostMapping("/plan/list/{goPage}/{pageSize}")
public Pager<List<TestPlanCaseDTO>> getTestPlanCases(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody QueryTestCaseRequest request){
Page<Object> page = PageHelper.startPage(goPage, pageSize, true);
return PageUtils.setPageInfo(page, testCaseService.getTestPlanCases(request));
}
@PostMapping("/name/all")
public List<TestCase> getTestCaseNames(@RequestBody QueryTestCaseRequest request){
return testCaseService.getTestCaseNames(request);

View File

@ -7,6 +7,7 @@ import io.metersphere.base.domain.TestCaseWithBLOBs;
import io.metersphere.base.domain.TestPlan;
import io.metersphere.commons.utils.PageUtils;
import io.metersphere.commons.utils.Pager;
import io.metersphere.controller.request.testcase.PlanCaseRelevanceRequest;
import io.metersphere.controller.request.testcase.QueryTestPlanRequest;
import io.metersphere.dto.TestPlanDTO;
import io.metersphere.service.TestCaseService;
@ -49,5 +50,10 @@ public class TestPlanController {
return testPlanService.deleteTestPlan(testPlanId);
}
@PostMapping("/relevance")
public void testPlanRelevance(@RequestBody PlanCaseRelevanceRequest request){
testPlanService.testPlanRelevance(request);
}
}

View File

@ -0,0 +1,26 @@
package io.metersphere.controller.request.testcase;
import java.util.ArrayList;
import java.util.List;
public class PlanCaseRelevanceRequest {
private String planId;
private List<String> testCaseIds = new ArrayList<>();
public String getPlanId() {
return planId;
}
public void setPlanId(String planId) {
this.planId = planId;
}
public List<String> getTestCaseIds() {
return testCaseIds;
}
public void setTestCaseIds(List<String> testCaseIds) {
this.testCaseIds = testCaseIds;
}
}

View File

@ -7,7 +7,6 @@ import java.util.List;
public class QueryTestPlanRequest extends TestPlan {
private String workspaceId;
private boolean recent = false;
public boolean isRecent() {
@ -17,12 +16,4 @@ public class QueryTestPlanRequest extends TestPlan {
public void setRecent(boolean recent) {
this.recent = recent;
}
public String getWorkspaceId() {
return workspaceId;
}
public void setWorkspaceId(String workspaceId) {
this.workspaceId = workspaceId;
}
}

View File

@ -0,0 +1,25 @@
package io.metersphere.dto;
import io.metersphere.base.domain.TestCase;
public class TestPlanCaseDTO extends TestCase {
private String executor;
private String status;
public String getExecutor() {
return executor;
}
public void setExecutor(String executor) {
this.executor = executor;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
}

View File

@ -135,9 +135,10 @@ public class TestCaseNodeService {
.map(TestPlanTestCase::getCaseId)
.collect(Collectors.toList());
List<Integer> nodeIds = nodes.stream()
.filter(node -> caseIds.contains(node.getId()))
.map(TestCaseNode::getId)
TestCaseExample testCaseExample = new TestCaseExample();
testCaseExample.createCriteria().andIdIn(caseIds);
List<Integer> dataNodeIds = testCaseMapper.selectByExample(testCaseExample).stream()
.map(TestCase::getNodeId)
.collect(Collectors.toList());
List<TestCaseNodeDTO> nodeTrees = getNodeTrees(nodes);
@ -145,7 +146,7 @@ public class TestCaseNodeService {
Iterator<TestCaseNodeDTO> iterator = nodeTrees.iterator();
while(iterator.hasNext()){
TestCaseNodeDTO rootNode = iterator.next();
if(pruningTree(rootNode, nodeIds)){
if(pruningTree(rootNode, dataNodeIds)){
iterator.remove();
}
}
@ -163,6 +164,13 @@ public class TestCaseNodeService {
List<TestCaseNodeDTO> children = rootNode.getChildren();
if(children == null || children.isEmpty()){
//叶子节点,并且该节点无数据
if(!nodeIds.contains(rootNode.getId())){
return true;
}
}
if(children != null) {
Iterator<TestCaseNodeDTO> iterator = children.iterator();
while(iterator.hasNext()){
@ -171,11 +179,8 @@ public class TestCaseNodeService {
iterator.remove();
}
}
}
if(children == null || children.isEmpty()){
//叶子节点,并且该节点无数据
if(!nodeIds.contains(rootNode.getId())){
if (children.isEmpty() && !nodeIds.contains(rootNode.getId())) {
return true;
}
}

View File

@ -1,21 +1,23 @@
package io.metersphere.service;
import io.metersphere.base.domain.TestCase;
import io.metersphere.base.domain.TestCaseExample;
import io.metersphere.base.domain.TestCaseWithBLOBs;
import io.metersphere.base.domain.TestPlan;
import io.metersphere.base.domain.*;
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.controller.request.testcase.QueryTestCaseRequest;
import io.metersphere.dto.TestPlanCaseDTO;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
@Service
@Transactional(rollbackFor = Exception.class)
@ -27,6 +29,9 @@ public class TestCaseService {
@Resource
ExtTestCaseMapper extTestCaseMapper;
@Resource
TestPlanTestCaseMapper testPlanTestCaseMapper;
@Resource
TestPlanMapper testPlanMapper;
@ -75,10 +80,15 @@ public class TestCaseService {
public List<TestCase> getTestCaseNames(QueryTestCaseRequest request) {
if ( StringUtils.isNotBlank(request.getPlanId()) ) {
TestPlan testPlan = testPlanMapper.selectByPrimaryKey(request.getPlanId());
request.setProjectId(testPlan.getProjectId());
}
return extTestCaseMapper.getTestCaseNames(request);
}
public List<TestPlanCaseDTO> getTestPlanCases(QueryTestCaseRequest request) {
return extTestCaseMapper.getTestPlanTestCases(request);
}
}

View File

@ -4,18 +4,24 @@ package io.metersphere.service;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.TestCaseMapper;
import io.metersphere.base.mapper.TestPlanMapper;
import io.metersphere.base.mapper.TestPlanTestCaseMapper;
import io.metersphere.base.mapper.ext.ExtTestPlanMapper;
import io.metersphere.commons.constants.TestPlanStatus;
import io.metersphere.commons.constants.TestPlanTestCaseStatus;
import io.metersphere.controller.request.testcase.PlanCaseRelevanceRequest;
import io.metersphere.controller.request.testcase.QueryTestCaseRequest;
import io.metersphere.controller.request.testcase.QueryTestPlanRequest;
import io.metersphere.dto.TestPlanDTO;
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 javax.annotation.Resource;
import java.util.List;
import java.util.UUID;
import java.util.*;
import java.util.stream.Collectors;
@Service
@Transactional(rollbackFor = Exception.class)
@ -27,6 +33,15 @@ public class TestPlanService {
@Resource
ExtTestPlanMapper extTestPlanMapper;
@Resource
TestCaseMapper testCaseMapper;
@Resource
TestPlanTestCaseMapper testPlanTestCaseMapper;
@Resource
SqlSessionFactory sqlSessionFactory;
public void addTestPlan(TestPlan testPlan) {
testPlan.setId(UUID.randomUUID().toString());
testPlan.setStatus(TestPlanStatus.Prepare.name());
@ -54,4 +69,36 @@ public class TestPlanService {
public List<TestPlanDTO> listTestPlan(QueryTestPlanRequest request) {
return extTestPlanMapper.list(request);
}
public void testPlanRelevance(PlanCaseRelevanceRequest request) {
List<String> testCaseIds = request.getTestCaseIds();
TestCaseExample testCaseExample = new TestCaseExample();
testCaseExample.createCriteria().andIdIn(testCaseIds);
Map<String, TestCaseWithBLOBs> testCaseMap =
testCaseMapper.selectByExampleWithBLOBs(testCaseExample)
.stream()
.collect(Collectors.toMap(TestCase::getId, testcase -> testcase));
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
TestPlanTestCaseMapper batchMapper = sqlSession.getMapper(TestPlanTestCaseMapper.class);
if (!testCaseIds.isEmpty()) {
testCaseIds.forEach(caseId -> {
TestCaseWithBLOBs testCase = testCaseMap.get(caseId);
TestPlanTestCase testPlanTestCase = new TestPlanTestCase();
testPlanTestCase.setExecutor(testCase.getMaintainer());
testPlanTestCase.setCaseId(caseId);
testPlanTestCase.setCreateTime(System.currentTimeMillis());
testPlanTestCase.setUpdateTime(System.currentTimeMillis());
testPlanTestCase.setPlanId(request.getPlanId());
testPlanTestCase.setStatus(TestPlanTestCaseStatus.Prepare.name());
testPlanTestCase.setResults(testCase.getSteps());
batchMapper.insert(testPlanTestCase);
});
}
sqlSession.flushStatements();
}
}

View File

@ -15,6 +15,7 @@
@node-drag-end="handleDragEnd"
:filter-node-method="filterNode"
:expand-on-click-node="false"
highlight-current
draggable
ref="tree">
@ -230,7 +231,7 @@
justify-content: space-between;
font-size: 14px;
padding-right: 8px;
width: 100px;
width: 100%;
}
.node-tree {

View File

@ -65,7 +65,7 @@
<el-select v-model="form.type" :placeholder="$t('test_track.input_type')">
<el-option :label="$t('commons.functional')" value="functional"></el-option>
<el-option :label="$t('commons.performance')" value="performance"></el-option>
<el-option :label="$t('commons.interface')" value="interface"></el-option>
<el-option :label="$t('commons.api')" value="api"></el-option>
</el-select>
</el-form-item>
</el-col>

View File

@ -55,7 +55,7 @@
<template v-slot:default="scope">
<span v-if="scope.row.type == 'functional'">{{$t('commons.functional')}}</span>
<span v-if="scope.row.type == 'performance'">{{$t('commons.performance')}}</span>
<span v-if="scope.row.type == 'interface'">{{$t('commons.interface')}}</span>
<span v-if="scope.row.type == 'api'">{{$t('commons.api')}}</span>
</template>
</el-table-column>
<el-table-column

View File

@ -2,25 +2,33 @@
<div class="plan_container">
<el-container>
<el-aside width="250px">
<el-aside class="node-tree" width="250px">
<plan-node-tree
:plan-id="planId"
class="node_tree"
@nodeSelectEvent="get"
ref="nodeTree"></plan-node-tree>
:tree-nodes="treeNodes"
@nodeSelectEvent="getPlanCases"
ref="tree"></plan-node-tree>
</el-aside>
<el-main>
<test-case-plan-list
@openTestCaseRelevanceDialog="openTestCaseRelevanceDialog"
@editTestPlanTestCase="editTestPlanTestCase"
:plan-id="planId"
ref="testCasePlanList"></test-case-plan-list>
</el-main>
</el-container>
<test-case-relevance
@refresh="getCaseByNodeIds"
@refresh="getPlanCases"
:plan-id="planId"
ref="testCaseRelevance"></test-case-relevance>
<test-plan-test-case-edit
ref="testPlanTestCaseEdit">
</test-plan-test-case-edit>
</div>
</template>
@ -30,15 +38,19 @@
import PlanNodeTree from "./components/PlanNodeTree";
import TestCasePlanList from "./components/TestCasePlanList";
import TestCaseRelevance from "./components/TestCaseRelevance";
import TestPlanTestCaseEdit from "./components/TestPlanTestCaseEdit";
export default {
name: "TestPlanView",
components: {PlanNodeTree, TestCasePlanList, TestCaseRelevance},
components: {PlanNodeTree, TestCasePlanList, TestCaseRelevance, TestPlanTestCaseEdit},
data() {
return {
currentProject: {}
treeNodes: []
}
},
created() {
this.getNodeTreeByPlanId();
},
computed: {
planId: function () {
return this.$route.params.planId;
@ -48,14 +60,21 @@
refresh() {
},
get() {
getPlanCases(nodeIds) {
this.$refs.testCasePlanList(nodeIds);
},
openTestCaseRelevanceDialog() {
this.$refs.testCaseRelevance.openTestCaseRelevanceDialog(this.planId);
},
getCaseByNodeIds() {
getNodeTreeByPlanId() {
if(this.planId){
this.$get("/case/node/list/plan/" + this.planId, response => {
this.treeNodes = response.data;
});
}
},
editTestPlanTestCase() {
this.$refs.testPlanTestCaseEdit.drawer = true;
}
}
}
@ -68,5 +87,9 @@
height: 600px;
}
.node-tree {
margin-top: 2%;
margin-left: 15px;
}
</style>

View File

@ -10,10 +10,13 @@
@node-drag-end="handleDragEnd"
:filter-node-method="filterNode"
:expand-on-click-node="false"
highlight-current
draggable
ref="tree">
<template @click="selectNode(node)" v-slot:default="{node}">
<template v-slot:default="{node}">
<span class="custom-tree-node" @click="selectNode(node)">
{{node.label}}
</span>
</template>
</el-tree>
</div>
@ -42,6 +45,11 @@
Array
},
},
watch: {
filterText(val) {
this.$refs.tree.filter(val);
}
},
selectNode(node) {
let nodeIds = [];
this.getChildNodeId(node, nodeIds);
@ -119,7 +127,7 @@
justify-content: space-between;
font-size: 14px;
padding-right: 8px;
width: 100px;
width: 100%;
}
.node-tree {

View File

@ -55,7 +55,7 @@
<template v-slot:default="scope">
<span v-if="scope.row.type == 'functional'">{{$t('commons.functional')}}</span>
<span v-if="scope.row.type == 'performance'">{{$t('commons.performance')}}</span>
<span v-if="scope.row.type == 'interface'">{{$t('commons.interface')}}</span>
<span v-if="scope.row.type == 'api'">{{$t('commons.api')}}</span>
</template>
</el-table-column>
<el-table-column
@ -68,19 +68,27 @@
<span v-if="scope.row.method == 'auto'">{{$t('test_track.auto')}}</span>
</template>
</el-table-column>
<el-table-column
prop="nodePath"
:label="$t('test_track.module')"
width="160"
prop="executor"
label="执行人">
</el-table-column>
<el-table-column
prop="status"
label="执行结果"
width="160"
show-overflow-tooltip>
</el-table-column>
<el-table-column
width="160"
:label="$t('commons.create_time')">
<template v-slot:default="scope">
<span>{{ scope.row.createTime | timestampFormatDate }}</span>
<span v-if="scope.row.status == 'Prepare'">未开始</span>
<span v-if="scope.row.status == 'Pass'">通过</span>
<span v-if="scope.row.status == 'Failure'">失败</span>
<span v-if="scope.row.status == 'Blocking'">阻塞</span>
<span v-if="scope.row.status == 'Skip'">跳过</span>
</template>
</el-table-column>
<el-table-column
width="160"
:label="$t('commons.update_time')">
@ -141,26 +149,29 @@
testId: null
}
},
props:{
planId: {
type: String
}
},
created: function () {
this.initTableData();
},
methods: {
initTableData(nodeIds) {
if (this.planId) {
let param = {
name: this.condition,
};
param.nodeIds = nodeIds;
if(localStorage.getItem(CURRENT_PROJECT)) {
param.projectId = JSON.parse(localStorage.getItem(CURRENT_PROJECT)).id;
}
this.$post(this.buildPagePath('/test/case/list'), param, response => {
param.planId = this.planId;
this.$post(this.buildPagePath('/test/case/plan/list'), param, response => {
this.loadingRequire.testCase = false;
let data = response.data;
this.total = data.itemCount;
this.tableData = data.listObject;
});
}
},
search() {
this.initTableData();
@ -180,7 +191,7 @@
this.multipleSelection = val;
},
handleEdit(testCase) {
this.$emit('testCaseRelevance', testCase);
this.$emit('editTestPlanTestCase', testCase);
},
handleDelete(testCase) {
this.$alert(this.$t('load_test.delete_confirm') + testCase.name + "", '', {

View File

@ -4,12 +4,14 @@
<el-dialog title="关联测试用例"
:visible.sync="dialogFormVisible"
width="65%">
width="50%">
<el-container class="main-content">
<el-aside class="node-tree" width="250px">
<plan-node-tree
:tree-nodes="treeNodes" ref="tree"></plan-node-tree>
:tree-nodes="treeNodes"
@nodeSelectEvent="getCaseNameByNodeIds"
ref="tree"></plan-node-tree>
</el-aside>
<el-container>
@ -23,16 +25,12 @@
ref="table">
<el-table-column
type="selection"
:reserve-selection="true"></el-table-column>
type="selection"></el-table-column>
<el-table-column
prop="name"
label="用例名称"
style="width: 100%">
<template v-slot:header>
用例名称
</template>
<template v-slot:default="scope">
{{scope.row.name}}
</template>
@ -79,28 +77,45 @@
treeNodes: []
};
},
props: {
planId: {
type: String
}
},
methods: {
openTestCaseRelevanceDialog(planId) {
console.log(planId);
this.getNodeTreeByPlanId(planId);
this.getAllNodeTreeByPlanId(planId);
console.log(this.$refs);
this.getCaseNames(planId);
this.dialogFormVisible = true;
},
saveCaseRelevance(){
},
getCaseNames(planId) {
if(planId){
let param = {};
param.planId = this.planId;
param.testCaseIds = [...this.selectIds];
this.$post('/test/plan/relevance' , param, () => {
this.$message.success("保存成功");
});
},
getCaseNames(planId, nodeIds) {
let param = {};
if (planId) {
param.planId = planId;
}
if (nodeIds && nodeIds.length > 0){
param.nodeIds = nodeIds;
}
this.$post('/test/case/name/all', param, response => {
this.testCases = response.data;
this.testCases.forEach(item => {
item.checked = false;
});
});
}
},
getCaseNameByNodeIds(nodeIds) {
this.dialogFormVisible = true;
this.getCaseNames(null, nodeIds);
},
checkAll() {
this.testCases.forEach(item => {
@ -124,14 +139,13 @@
this.selectIds.add(row.id);
}
},
getNodeTreeByPlanId(planId) {
getAllNodeTreeByPlanId(planId) {
if (planId) {
this.$get("/case/node/list/all/plan/" + planId, response => {
this.treeNodes = response.data;
});
}
}
}
}
</script>
@ -155,7 +169,7 @@
/*border-radius: 1px;*/
/*padding-top: 5px ;*/
/*height: 100%;*/
margin-right: 5px;
margin-right: 10px;
/*box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04);*/
}
@ -163,7 +177,6 @@
background-color: darkgrey;
color: #333;
line-height: 60px;
height: 1%;
}
.el-aside {

View File

@ -148,7 +148,7 @@
testId: null,
}
},
created: function () {
created() {
this.projectId = this.$route.params.projectId;
this.initTableData();
},

View File

@ -0,0 +1,29 @@
<template>
<div>
<el-drawer
title="我是标题"
:visible.sync="drawer"
:direction="direction"
:before-close="handleClose">
<span>我来啦!</span>
</el-drawer>
</div>
</template>
<script>
export default {
name: "TestPlanTestCaseEdit",
data() {
return {
drawer: false,
direction: 'rtl',
};
}
}
</script>
<style scoped>
</style>

View File

@ -40,7 +40,6 @@ export default {
'system_setting': 'Settings',
'api': 'Api',
'performance': 'Performance',
'interface': 'Interface test',
'input_content': 'Please enter content',
'create': 'create',
'refresh': 'refresh',

View File

@ -40,7 +40,6 @@ export default {
'system_setting': '系统设置',
'api': '接口测试',
'performance': '性能测试',
'interface': '接口测试',
'input_content': '请输入内容',
'create': '新建',
'refresh': '刷新',