测试计划关联用例及执行

This commit is contained in:
liangdaliang
2025-04-27 23:36:48 +08:00
parent b680339e6a
commit 18434d1f88
19 changed files with 1153 additions and 10 deletions

View File

@@ -8,6 +8,7 @@ import com.test.common.enums.BusinessType;
import com.test.common.utils.DateUtils;
import com.test.common.utils.uuid.IdUtils;
import com.test.test.domain.TestCase;
import com.test.test.domain.TestPlanCase;
import com.test.test.domain.qo.IDQO;
import com.test.test.domain.qo.TestCaseListQO;
import com.test.test.service.ITestCaseService;
@@ -44,6 +45,16 @@ public class TestCaseController extends BaseController {
return getDataTable(list);
}
/**
* 查询测试计划可关联的用例列表
*/
@GetMapping("/planRelateList")
public TableDataInfo planRelateList(TestCaseListQO qo) {
startPage();
List<TestCase> list = testCaseService.selectValidTestPlanCaseList(qo);
return getDataTable(list);
}
/**
* 获取用例详细信息
*/
@@ -108,4 +119,18 @@ public class TestCaseController extends BaseController {
testCaseService.executeTestCaseById(qo.getId(), jmeterHomePath, caseSid);
return AjaxResult.success("执行成功", caseSid);
}
/**
* 异步执行测试计划用例
*/
@Log(title = "用例", businessType = BusinessType.OTHER)
@PostMapping("/runTestPlanCase")
public AjaxResult runTestPlanCase(@RequestBody TestPlanCase testPlanCase) {
String caseSid = IdUtils.simpleUUID();
// 异步执行任务
CompletableFuture.runAsync(() -> {
testCaseService.executeTestCaseByPlanCase(testPlanCase, jmeterHomePath, caseSid);
});
return toAjax(true);
}
}

View File

@@ -0,0 +1,91 @@
package com.test.test.controller;
import com.test.common.annotation.Log;
import com.test.common.core.controller.BaseController;
import com.test.common.core.domain.AjaxResult;
import com.test.common.core.page.TableDataInfo;
import com.test.common.enums.BusinessType;
import com.test.test.domain.TestPlanCase;
import com.test.test.domain.qo.TestPlanCaseQO;
import com.test.test.service.ITestPlanCaseService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 测试计划用例关联Controller
*
* @author test
* @date 2025-04-25
*/
@RestController
@RequestMapping("/test/planCase")
public class TestPlanCaseController extends BaseController
{
@Autowired
private ITestPlanCaseService testPlanCaseService;
/**
* 查询测试计划用例关联列表
*/
@GetMapping("/list")
public TableDataInfo list(TestPlanCase testPlanCase)
{
startPage();
List<TestPlanCase> list = testPlanCaseService.selectTestPlanCaseList(testPlanCase);
return getDataTable(list);
}
/**
* 导出测试计划用例关联列表
*/
// @Log(title = "测试计划用例关联", businessType = BusinessType.EXPORT)
// @PostMapping("/export")
// public void export(HttpServletResponse response, TestPlanCase testPlanCase)
// {
// List<TestPlanCase> list = testPlanCaseService.selectTestPlanCaseList(testPlanCase);
// ExcelUtil<TestPlanCase> util = new ExcelUtil<TestPlanCase>(TestPlanCase.class);
// util.exportExcel(response, list, "测试计划用例关联数据");
// }
/**
* 获取测试计划用例关联详细信息
*/
// @GetMapping(value = "/{id}")
// public AjaxResult getInfo(@PathVariable("id") Long id)
// {
// return success(testPlanCaseService.selectTestPlanCaseById(id));
// }
/**
* 新增测试计划用例关联
*/
@Log(title = "测试计划用例关联", businessType = BusinessType.INSERT)
@PostMapping(value = "/saveRelate")
public AjaxResult saveRelate(@RequestBody TestPlanCaseQO qo)
{
testPlanCaseService.saveRelate(qo);
return success(qo);
}
/**
* 修改测试计划用例关联
*/
// @Log(title = "测试计划用例关联", businessType = BusinessType.UPDATE)
// @PutMapping
// public AjaxResult edit(@RequestBody TestPlanCase testPlanCase)
// {
// return toAjax(testPlanCaseService.updateTestPlanCase(testPlanCase));
// }
/**
* 删除测试计划用例关联
*/
@Log(title = "测试计划用例关联", businessType = BusinessType.DELETE)
@PostMapping("/delRelate/{ids}")
public AjaxResult remove(@PathVariable("ids") Long[] ids)
{
return toAjax(testPlanCaseService.deleteTestPlanCaseByIds(ids));
}
}

View File

@@ -0,0 +1,61 @@
package com.test.test.domain;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.test.common.annotation.Excel;
import com.test.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
/**
* 测试计划用例关联对象 test_plan_case
*
* @author test
* @date 2025-04-25
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class TestPlanCase extends BaseEntity
{
private static final long serialVersionUID = 1L;
/** 主键 */
private Long id;
/** 测试计划主键ID */
@Excel(name = "测试计划主键ID")
private Long planId;
/** 用例主键ID */
@Excel(name = "用例主键ID")
private Long caseId;
/** 测试用例类型(0,冒烟测试,1,功能测试,2,回归测试,3,准生产测试,4,生产验证) */
@Excel(name = "测试用例类型(0,冒烟测试,1,功能测试,2,回归测试,3,准生产测试,4,生产验证)")
private Integer type;
/** 未执行、成功或失败 */
@Excel(name = "未执行、成功或失败")
private String executeResult;
/** 执行时间 */
@JsonFormat(pattern = "yyyy-MM-dd")
@Excel(name = "执行时间", width = 30, dateFormat = "yyyy-MM-dd")
private Date executeTime;
/** 执行人 */
@Excel(name = "执行人")
private String executeBy;
/** 版本 */
@Excel(name = "版本")
private String version;
/** 0,正常,1,删除 */
private String delFlag;
/** 未执行、成功或失败 */
@Excel(name = "用例名称")
private String caseName;
}

View File

@@ -3,8 +3,18 @@ package com.test.test.domain.qo;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.io.Serializable;
@Data
public class TestCaseListQO {
public class TestCaseListQO implements Serializable {
private static final long serialVersionUID = 4636502018355979101L;
@NotNull(message = "父节点id不能为空")
private Long groupId;
/** 测试计划主键ID */
private Long planId;
/**
* 测试用例类型(0,冒烟测试,1,功能测试,2,回归测试,3,准生产测试,4,生产验证)
*/
private Integer type;
}

View File

@@ -0,0 +1,20 @@
package com.test.test.domain.qo;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* @author liangdaliang
* @Description测试计划关联用例
* @date 2025-04-27 13:16
*/
@Data
public class TestPlanCaseQO implements Serializable {
private static final long serialVersionUID = 4636202018365979101L;
private Long planId;
private Integer type;
private List<Long> testCaseIdList;
}

View File

@@ -20,6 +20,11 @@ public interface TestCaseMapper
*/
List<TestCase> selectTestCaseList(TestCaseListQO qo);
/**
* 查询测试计划可关联的用例列表
*/
List<TestCase> selectValidTestPlanCaseList(TestCaseListQO qo);
/**
* 查询用例列表
*/

View File

@@ -0,0 +1,62 @@
package com.test.test.mapper;
import com.test.test.domain.TestPlanCase;
import java.util.List;
/**
* 测试计划用例关联Mapper接口
*
* @author test
* @date 2025-04-25
*/
public interface TestPlanCaseMapper
{
/**
* 查询测试计划用例关联
*
* @param id 测试计划用例关联主键
* @return 测试计划用例关联
*/
public TestPlanCase selectTestPlanCaseById(Long id);
/**
* 查询测试计划用例关联列表
*
* @param testPlanCase 测试计划用例关联
* @return 测试计划用例关联集合
*/
public List<TestPlanCase> selectTestPlanCaseList(TestPlanCase testPlanCase);
/**
* 新增测试计划用例关联
*
* @param testPlanCase 测试计划用例关联
* @return 结果
*/
public int insertTestPlanCase(TestPlanCase testPlanCase);
/**
* 修改测试计划用例关联
*
* @param testPlanCase 测试计划用例关联
* @return 结果
*/
public int updateTestPlanCase(TestPlanCase testPlanCase);
/**
* 删除测试计划用例关联
*
* @param id 测试计划用例关联主键
* @return 结果
*/
public int deleteTestPlanCaseById(Long id);
/**
* 批量删除测试计划用例关联
*
* @param ids 需要删除的数据主键集合
* @return 结果
*/
public int deleteTestPlanCaseByIds(Long[] ids);
}

View File

@@ -1,6 +1,7 @@
package com.test.test.service;
import com.test.test.domain.TestCase;
import com.test.test.domain.TestPlanCase;
import com.test.test.domain.qo.TestCaseListQO;
import java.util.List;
@@ -29,6 +30,13 @@ public interface ITestCaseService
*/
public List<TestCase> selectTestCaseList(TestCaseListQO qo);
/**
* 查询测试计划可关联的用例列表
* @param qo 用例
* @return 用例集合
*/
public List<TestCase> selectValidTestPlanCaseList(TestCaseListQO qo);
/**
* 查询用例列表
* @param testCase
@@ -77,4 +85,14 @@ public interface ITestCaseService
* @return 是否成功
*/
public String executeTestCaseById(Long id, String jmeterHomePath, String caseSid);
/**
* 执行测试计划关联的用例
*
* @param testPlanCase 测试计划关联用例查询条件
* @param jmeterHomePath jmeter安装路径
* @param caseSid 用例执行uuid
* @return 是否成功
*/
public void executeTestCaseByPlanCase(TestPlanCase testPlanCase, String jmeterHomePath, String caseSid);
}

View File

@@ -0,0 +1,71 @@
package com.test.test.service;
import com.test.test.domain.TestPlanCase;
import com.test.test.domain.qo.TestPlanCaseQO;
import java.util.List;
/**
* 测试计划用例关联Service接口
*
* @author test
* @date 2025-04-25
*/
public interface ITestPlanCaseService
{
/**
* 查询测试计划用例关联
*
* @param id 测试计划用例关联主键
* @return 测试计划用例关联
*/
public TestPlanCase selectTestPlanCaseById(Long id);
/**
* 查询测试计划用例关联列表
*
* @param testPlanCase 测试计划用例关联
* @return 测试计划用例关联集合
*/
public List<TestPlanCase> selectTestPlanCaseList(TestPlanCase testPlanCase);
/**
* 保存测试计划用例关联列表
*
* @param testPlanCaseQO 测试计划用例关联
* @return
*/
public void saveRelate(TestPlanCaseQO testPlanCaseQO);
/**
* 新增测试计划用例关联
*
* @param testPlanCase 测试计划用例关联
* @return 结果
*/
public int insertTestPlanCase(TestPlanCase testPlanCase);
/**
* 修改测试计划用例关联
*
* @param testPlanCase 测试计划用例关联
* @return 结果
*/
public int updateTestPlanCase(TestPlanCase testPlanCase);
/**
* 批量删除测试计划用例关联
*
* @param ids 需要删除的测试计划用例关联主键集合
* @return 结果
*/
public int deleteTestPlanCaseByIds(Long[] ids);
/**
* 删除测试计划用例关联信息
*
* @param id 测试计划用例关联主键
* @return 结果
*/
public int deleteTestPlanCaseById(Long id);
}

View File

@@ -4,17 +4,11 @@ import com.fatboyindustrial.gsonjavatime.LocalDateTimeConverter;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import com.test.common.utils.DateUtils;
import com.test.common.utils.JMeterUtil;
import com.test.common.utils.MySQLExecutor;
import com.test.common.utils.StringUtils;
import com.test.common.utils.*;
import com.test.common.utils.sql.TinyIntTypeAdapter;
import com.test.test.domain.*;
import com.test.test.domain.qo.*;
import com.test.test.mapper.TestCaseLogMapper;
import com.test.test.mapper.TestCaseMapper;
import com.test.test.mapper.TestCaseResultMapper;
import com.test.test.mapper.TestDatasourceMapper;
import com.test.test.mapper.*;
import com.test.test.service.ITestCaseService;
import com.test.test.service.ITestCaseStepService;
import jakarta.annotation.Resource;
@@ -49,6 +43,10 @@ public class TestCaseServiceImpl implements ITestCaseService
private TestDatasourceMapper testDatasourceMapper;
@Resource
private TestCaseLogMapper testCaseLogMapper;
@Resource
private TestPlanMapper testPlanMapper;
@Resource
private TestPlanCaseMapper testPlanCaseMapper;
@Autowired
private ITestCaseStepService testCaseStepService;
@@ -76,6 +74,14 @@ public class TestCaseServiceImpl implements ITestCaseService
return testCaseMapper.selectTestCaseList(qo);
}
/**
* 查询测试计划可关联的用例列表
*/
@Override
public List<TestCase> selectValidTestPlanCaseList(TestCaseListQO qo) {
return testCaseMapper.selectValidTestPlanCaseList(qo);
}
@Override
public List<TestCase> selectTestCaseList(TestCase testCase) {
return testCaseMapper.selectTestCaseListNoGroupId(testCase);
@@ -201,6 +207,102 @@ public class TestCaseServiceImpl implements ITestCaseService
return caseSid;
}
/**
* 执行测试计划关联的用例
*
* @param testPlanCase 测试计划关联用例查询条件
* @param jmeterHomePath jmeter安装路径
* @param caseSid 用例执行uuid
* @return 是否成功
*/
@Override
public void executeTestCaseByPlanCase(TestPlanCase testPlanCase, String jmeterHomePath, String caseSid) {
List<TestPlanCase> testPlanCaseList = testPlanCaseMapper.selectTestPlanCaseList(testPlanCase);
if (CollectionUtils.isEmpty(testPlanCaseList)) {
log.error("计划关联测试用例为空,不能执行!");
return ;
}
TestPlan testPlan = testPlanMapper.selectTestPlanById(testPlanCase.getPlanId());
testPlan.setStatus("1");
testPlan.setUpdateTime(DateUtils.getNowDate());
testPlanMapper.updateTestPlan(testPlan);
for (TestPlanCase input : testPlanCaseList) {
boolean isSuccess = true;
input.setExecuteTime(DateUtils.getNowDate());
input.setExecuteBy(SecurityUtils.getUsername());
TestCase testCase = this.selectTestCaseById(input.getCaseId());
Long id = input.getCaseId();
Map<String, String> contextResultMap = new HashMap<>();
testCase.setContextResultMap(contextResultMap);
TestCaseLog testCaseLog = new TestCaseLog();
testCaseLog.setCaseId(id);
testCaseLog.setOperTime(DateUtils.getNowDate());
testCaseLog.setOperType("执行");
testCaseLog.setCaseSid(caseSid);
testCase.setCaseSid(caseSid);
List<TestCaseStep> testCaseStepList = testCaseStepService.selectTestCaseStepListByCaseId(id);
for (TestCaseStep testCaseStep : testCaseStepList) {
if (testCaseStep.getType() == 1L) {
// http接口处理
boolean httpTestResult = doHttpRequestTest(testCase, testCaseStep, jmeterHomePath);
if (!httpTestResult) {
testCaseLog.setOperDetail("失败");
testCaseLogMapper.insertTestCaseLog(testCaseLog);
log.error("用例步骤:{}执行失败!", testCaseStep.getName());
isSuccess = false;
break;
}
} else if (testCaseStep.getType() == 2L) {
// 数据库接口处理
boolean dateSourceTestResult = doDateSourceRequestTest(testCase, testCaseStep);
if (!dateSourceTestResult) {
testCaseLog.setOperDetail("失败");
testCaseLogMapper.insertTestCaseLog(testCaseLog);
log.error("数据源用例步骤:{}执行失败!", testCaseStep.getName());
isSuccess = false;
break;
}
} else if (testCaseStep.getType() == 3L) {
// 循环节点处理
boolean pollTestResult = doPollRequestTest(testCase, testCaseStep, jmeterHomePath);
if (!pollTestResult) {
testCaseLog.setOperDetail("失败");
testCaseLogMapper.insertTestCaseLog(testCaseLog);
log.error("循环用例步骤:{}执行失败!", testCaseStep.getName());
isSuccess = false;
break;
}
} else if (testCaseStep.getType() == 4L) {
// 轮询节点处理
boolean loopTestResult = doLoopRequestTest(testCase, testCaseStep, jmeterHomePath);
if (!loopTestResult) {
testCaseLog.setOperDetail("失败");
testCaseLogMapper.insertTestCaseLog(testCaseLog);
log.error("轮询用例步骤:{}执行失败!", testCaseStep.getName());
isSuccess = false;
break;
}
} else {
log.error("错误的用例步骤类型!");
isSuccess = false;
break;
}
}
if (isSuccess) {
testCaseLog.setOperDetail("成功");
input.setExecuteResult("成功");
} else {
testCaseLog.setOperDetail("失败");
input.setExecuteResult("失败");
}
testCaseLogMapper.insertTestCaseLog(testCaseLog);
testPlanCaseMapper.updateTestPlanCase(input);
}
testPlan.setUpdateTime(DateUtils.getNowDate());
testPlan.setStatus("2");
testPlanMapper.updateTestPlan(testPlan);
}
/**
* 处理http接口测试
* @param testCase

View File

@@ -0,0 +1,126 @@
package com.test.test.service.impl;
import com.test.common.utils.DateUtils;
import com.test.common.utils.SecurityUtils;
import com.test.test.domain.TestPlanCase;
import com.test.test.domain.qo.TestPlanCaseQO;
import com.test.test.mapper.TestPlanCaseMapper;
import com.test.test.service.ITestPlanCaseService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.List;
/**
* 测试计划用例关联Service业务层处理
*
* @author test
* @date 2025-04-25
*/
@Service
public class TestPlanCaseServiceImpl implements ITestPlanCaseService
{
@Resource
private TestPlanCaseMapper testPlanCaseMapper;
/**
* 查询测试计划用例关联
*
* @param id 测试计划用例关联主键
* @return 测试计划用例关联
*/
@Override
public TestPlanCase selectTestPlanCaseById(Long id)
{
return testPlanCaseMapper.selectTestPlanCaseById(id);
}
/**
* 查询测试计划用例关联列表
*
* @param testPlanCase 测试计划用例关联
* @return 测试计划用例关联
*/
@Override
public List<TestPlanCase> selectTestPlanCaseList(TestPlanCase testPlanCase)
{
return testPlanCaseMapper.selectTestPlanCaseList(testPlanCase);
}
/**
* 保存测试计划用例关联列表
*
* @param testPlanCaseQO 测试计划用例关联
* @return
*/
@Override
public void saveRelate(TestPlanCaseQO testPlanCaseQO)
{
List<Long> testCaseIdList = testPlanCaseQO.getTestCaseIdList();
for (Long testCaseId : testCaseIdList) {
TestPlanCase testPlanCase = new TestPlanCase();
testPlanCase.setPlanId(testPlanCaseQO.getPlanId());
testPlanCase.setCaseId(testCaseId);
testPlanCase.setType(testPlanCaseQO.getType());
List<TestPlanCase> testPlanCaseList = selectTestPlanCaseList(testPlanCase);
if (!CollectionUtils.isEmpty(testPlanCaseList)) {
// 防止重复
continue;
}
testPlanCase.setCreateBy(SecurityUtils.getUsername());
testPlanCase.setCreateTime(DateUtils.getNowDate());
testPlanCaseMapper.insertTestPlanCase(testPlanCase);
}
}
/**
* 新增测试计划用例关联
*
* @param testPlanCase 测试计划用例关联
* @return 结果
*/
@Override
public int insertTestPlanCase(TestPlanCase testPlanCase)
{
testPlanCase.setCreateTime(DateUtils.getNowDate());
return testPlanCaseMapper.insertTestPlanCase(testPlanCase);
}
/**
* 修改测试计划用例关联
*
* @param testPlanCase 测试计划用例关联
* @return 结果
*/
@Override
public int updateTestPlanCase(TestPlanCase testPlanCase)
{
testPlanCase.setUpdateTime(DateUtils.getNowDate());
return testPlanCaseMapper.updateTestPlanCase(testPlanCase);
}
/**
* 批量删除测试计划用例关联
*
* @param ids 需要删除的测试计划用例关联主键
* @return 结果
*/
@Override
public int deleteTestPlanCaseByIds(Long[] ids)
{
return testPlanCaseMapper.deleteTestPlanCaseByIds(ids);
}
/**
* 删除测试计划用例关联信息
*
* @param id 测试计划用例关联主键
* @return 结果
*/
@Override
public int deleteTestPlanCaseById(Long id)
{
return testPlanCaseMapper.deleteTestPlanCaseById(id);
}
}

View File

@@ -41,6 +41,30 @@
order by create_time desc
</select>
<select id="selectValidTestPlanCaseList" parameterType="TestCaseListQO" resultMap="TestCaseResult">
select tc.id,
tc.group_id,
tc.project_id,
tc.name,
tc.importance,
tc.status,
tc.del_flag,
tc.create_by,
tc.create_time,
tc.update_by,
tc.update_time
from test_case tc
where tc.del_flag = '0'
<if test="groupId != null and groupId != ''">
and tc.group_id = #{groupId}
</if>
and not exists(select 1 from test_plan_case tp
where tc.id = tp.case_id
and tp.plan_id = #{planId}
and tp.type = #{type})
order by tc.create_time desc
</select>
<select id="selectTestCaseListNoGroupId" parameterType="TestCase" resultMap="TestCaseResult">
<include refid="selectTestCaseVo"/>
<where>

View File

@@ -0,0 +1,111 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.test.test.mapper.TestPlanCaseMapper">
<resultMap type="TestPlanCase" id="TestPlanCaseResult">
<result property="id" column="id" />
<result property="planId" column="plan_id" />
<result property="caseId" column="case_id" />
<result property="type" column="type" />
<result property="executeResult" column="execute_result" />
<result property="createTime" column="create_time" />
<result property="createBy" column="create_by" />
<result property="updateTime" column="update_time" />
<result property="executeTime" column="execute_time" />
<result property="executeBy" column="execute_by" />
<result property="version" column="version" />
<result property="delFlag" column="del_flag" />
<result property="caseName" column="name" />
</resultMap>
<sql id="selectTestPlanCaseVo">
select id, plan_id, case_id, type, execute_result, create_time, create_by, update_time, execute_time, execute_by, version, del_flag from test_plan_case
</sql>
<select id="selectTestPlanCaseList" parameterType="TestPlanCase" resultMap="TestPlanCaseResult">
SELECT
tp.id,
tp.case_id,
tc.name ,
tp.execute_result,
tp.create_by,
tp.execute_by,
tp.execute_time,
tp.create_time
FROM test_plan_case tp
LEFT JOIN test_case tc ON tc.id = tp.case_id
where tp.del_flag = '0'
<if test="caseName != null and caseName != ''"> AND tc.name LIKE concat('%', #{caseName}, '%') </if>
<if test="caseId != null"> and case_id = #{caseId}</if>
<if test="type != null"> and type = #{type}</if>
<if test="planId != null"> and plan_id = #{planId}</if>
</select>
<select id="selectTestPlanCaseById" parameterType="Long" resultMap="TestPlanCaseResult">
<include refid="selectTestPlanCaseVo"/>
where id = #{id}
</select>
<insert id="insertTestPlanCase" parameterType="TestPlanCase">
insert into test_plan_case
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="id != null">id,</if>
<if test="planId != null and planId != ''">plan_id,</if>
<if test="caseId != null and caseId != ''">case_id,</if>
type,
<if test="executeResult != null">execute_result,</if>
<if test="createTime != null">create_time,</if>
<if test="createBy != null">create_by,</if>
<if test="updateTime != null">update_time,</if>
<if test="executeTime != null">execute_time,</if>
<if test="executeBy != null">execute_by,</if>
<if test="version != null">version,</if>
<if test="delFlag != null">del_flag,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">#{id},</if>
<if test="planId != null and planId != ''">#{planId},</if>
<if test="caseId != null and caseId != ''">#{caseId},</if>
#{type},
<if test="executeResult != null">#{executeResult},</if>
<if test="createTime != null">#{createTime},</if>
<if test="createBy != null">#{createBy},</if>
<if test="updateTime != null">#{updateTime},</if>
<if test="executeTime != null">#{executeTime},</if>
<if test="executeBy != null">#{executeBy},</if>
<if test="version != null">#{version},</if>
<if test="delFlag != null">#{delFlag},</if>
</trim>
</insert>
<update id="updateTestPlanCase" parameterType="TestPlanCase">
update test_plan_case
<trim prefix="SET" suffixOverrides=",">
<if test="planId != null and planId != ''">plan_id = #{planId},</if>
<if test="caseId != null and caseId != ''">case_id = #{caseId},</if>
<if test="type != null and type != ''">type = #{type},</if>
<if test="executeResult != null">execute_result = #{executeResult},</if>
<if test="createTime != null">create_time = #{createTime},</if>
<if test="createBy != null">create_by = #{createBy},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
<if test="executeTime != null">execute_time = #{executeTime},</if>
<if test="executeBy != null">execute_by = #{executeBy},</if>
<if test="version != null">version = #{version},</if>
<if test="delFlag != null">del_flag = #{delFlag},</if>
</trim>
where id = #{id}
</update>
<delete id="deleteTestPlanCaseById" parameterType="Long">
delete from test_plan_case where id = #{id}
</delete>
<delete id="deleteTestPlanCaseByIds" parameterType="String">
delete from test_plan_case where id in
<foreach item="id" collection="array" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
</mapper>

View File

@@ -0,0 +1,51 @@
import request from '@/utils/request'
const api = {
planCaseList: 'test/planCase/list',
relateCaseList: '/test/case/planRelateList',
saveRelate: 'test/planCase/saveRelate',
delRelate: 'test/planCase/delRelate/',
}
export function getPlanCaseList(data) {
return request({
url: api.planCaseList,
method: 'get',
params: data
})
}
// 查询可关联的用例列表
export function getRelateCaseList(data) {
return request({
url: api.relateCaseList,
method: 'get',
params: data
})
}
export function saveRelate(data) {
return request({
url: api.saveRelate,
method: 'post',
data
})
}
export function delRelate(id) {
return request({
url: api.delRelate + id,
method: 'post',
data: {id}
})
}
// 执行用例
export function runTestPlanCase(data) {
return request({
url: '/test/case/runTestPlanCase',
method: 'post',
data
})
}

View File

@@ -130,6 +130,20 @@ export const constantRoutes = [
}
]
},
{
path: '/testplan/execute',
component: Layout,
hidden: true,
children: [
{
path: '',
component: () => import('@/views/test/testplan/execute/index'),
name: 'PlanExecute',
noCache: true,
meta: { title: '计划执行', activeMenu: '/testplan' }
}
]
},
{
path: '/task/detail',
component: Layout,

View File

@@ -14,7 +14,7 @@
<el-table-column label="创建时间" align="center" prop="createTime"/>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-delete" @click.native.stop="handleRun(scope.row.id)">执行</el-button>
<el-button size="mini" type="text" icon="el-icon-caret-right" @click.native.stop="handleRun(scope.row.id)">执行</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click.native.stop="handleDelete(scope.row.id)">删除</el-button>
</template>
</el-table-column>

View File

@@ -0,0 +1,267 @@
<template>
<div class="app-container">
<!-- 顶部导航 -->
<el-row :gutter="10">
<el-col :span="24">
<el-header class="header">
<div class="head1">
<span style="font-size: 18px; font-weight: bold; margin-right: 20px;">[{{ title }}]关联用例列表</span>
</div>
<div class="head2">
<el-input :placeholder="'请输入用例名称'" v-model="queryParams.caseName" class="input-with-select" clearable>
<el-button slot="append" icon="el-icon-search" @click="handleQuery"></el-button>
</el-input>
<el-button icon="el-icon-plus" type="primary" size="medium" style="margin-left: 10px;" @click="relateCaseVue">
关联用例
</el-button>
<el-button type="primary" plain icon="el-icon-caret-right" size="medium" :disabled="isExecuteVisible" @click="handleRunAll">执行全部用例</el-button>
<el-button
type="danger"
plain
icon="el-icon-delete"
size="medium"
:disabled="multiple"
@click="handleDelete"
>批量删除</el-button>
</div>
</el-header>
</el-col>
</el-row>
<!-- 标签页 -->
<el-tabs v-model="activeTab" @tab-click="handleTabClick" style="margin-top: 10px;">
<el-tab-pane :label="'冒烟测试'" name="0"></el-tab-pane>
<el-tab-pane :label="'功能测试'" name="1"></el-tab-pane>
<el-tab-pane :label="'回归测试'" name="2"></el-tab-pane>
<el-tab-pane :label="'准生产验证'" name="3"></el-tab-pane>
<el-tab-pane :label="'生产验证'" name="4"></el-tab-pane>
<el-Table v-loading="loading" :data="list" @selection-change="handleSelectionChange">
<el-table-column type="selection"/>
<el-table-column prop="caseName" label="测试用例名称" align="center"/>
<el-table-column prop="executeResult" label="执行结果" align="center"/>
<el-table-column prop="createBy" label="创建人" align="center"/>
<el-table-column prop="executeBy" label="最近执行人" align="center"/>
<el-table-column prop="executeTime" label="最近执行时间" align="center"/>
<el-table-column prop="createTime" label="关联时间" align="center"/>
<el-table-column label="操作" align="left" fixed="right">
<template slot-scope="scope">
<!-- <el-button size="mini" type="text" icon="el-icon-caret-right" @click.native.stop="handleRun(scope.row.id)">执行</el-button>-->
<el-button size="mini" type="text" icon="el-icon-delete" @click.native.stop="handleDelete(scope.row)">删除
</el-button>
</template>
</el-table-column>
</el-Table>
</el-tabs>
<pagination
v-show="total > 0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<el-dialog title="关联测试用例" :visible.sync="open" width="1100px" v-loading="submitLoading" append-to-body>
<RelateCase ref="relateCase" :form="queryParams" @refresh="getList"></RelateCase>
<span slot="footer" class="dialog-footer">
<el-button @click="open = false"> </el-button>
<el-button type="primary" @click="submitForm"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import {getPlanCaseList, saveRelate, delRelate} from "@/api/test/planCase";
import SimpleOptions from "@/components/FormItem/option/SimpleOptions.vue";
import RelateCase from "@/views/test/testplan/execute/relateCase.vue";
import page1 from "../../case/detail/page1.vue";
import {runTestPlanCase} from "../../../../api/test/planCase";
export default {
name: 'project',
components: {page1, SimpleOptions, RelateCase},
dicts: ['priority_level', 'project_source', 'project_type', 'status'],
data() {
return {
activeTab: '0',
total: 0,
list: [],
title: '',
// 选中数组
ids: [],
isExecuteVisible: true,
// 非多个禁用
multiple: true,
// 遮罩层
loading: false,
submitLoading: false,
//弹窗
open: false,
selectedRows: [],
selectedData: [],
managerList: [],
queryParams: {
caseName: '',
planId: '',
type: 0,
pageNum: 1,
pageSize: 10,
},
activeNames: [], // 控制 collapse 的展开状态
};
},
created() {
this.queryParams.planId = this.$route.query.id;
this.title = this.$route.query.name;
this.getList();
},
methods: {
handleSelectionChange(selection) {
this.selectedRows = selection;
this.ids = selection.map(item => item.id)
this.multiple = !selection.length
},
handleTabClick(tab) {
this.ids = [];
this.multiple = false;
this.activeTab = tab.name;
this.queryParams.type = parseInt(this.activeTab, 10);
this.getList();
},
// 搜索
handleQuery() {
this.getList();
},
relateCaseVue() {
this.open = true;
this.reset();
this.$refs.relateCase.getList()
},
/** 查询列表 */
getList() {
this.loading = true;
const queryParams = {
...this.queryParams
}
getPlanCaseList(queryParams).then(list => {
this.list = list.rows;
this.total = list.total;
if (this.total > 0) {
this.isExecuteVisible = false;
} else {
this.isExecuteVisible = true;
}
this.loading = false
})
},
handleRunAll() {
this.loading = true;
const queryParams = {
planId: this.queryParams.planId,
type: this.queryParams.type
}
this.$modal.confirm('是否确认执行全部接口用例?').then(function () {
return runTestPlanCase(queryParams);
}).then((res) => {
this.$modal.msgSuccess("提交执行成功");
this.open = true;
this.loading = false;
});
},
// 表单重置
reset() {
this.selectedRows = [];
this.ids = [];
this.multiple = false;
},
submitForm() {
const form = {
...this.queryParams,
testCaseIdList: this.$refs.relateCase.getSelectedCaseIds()
}
this.submitLoading = true
saveRelate(form)
.then(() => {
this.$message.success('关联成功')
this.open = false
})
.catch(() => {
this.$message.error('关联失败')
})
.finally(() => {
this.submitLoading = false
this.open = false
this.getList()
})
},
/** 删除按钮操作 */
handleDelete(row) {
const ids = row.id || this.ids;
this.$modal.confirm('是否确认删除关联用例?').then(function () {
return delRelate(ids);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
});
}
}
};
</script>
<style lang="scss" scoped>
.input-with-select {
background-color: #ffffff;
width: 300px;
}
.collapse-search {
::v-deep .el-collapse-item__header {
display: none;
}
}
.high-search {
display: flex;
justify-content: space-between;
padding-top: 40px;
background-color: rgb(248, 248, 249);
}
.header {
padding: 10px;
display: flex;
justify-content: space-between;
align-items: center;
}
.head1 {
display: flex;
align-items: center;
}
.head2 {
display: flex;
align-items: center;
flex: 1;
justify-content: flex-end;
column-gap: 10px;
}
.el-dialog .el-form {
width: 100%;
}
.el-dialog .el-form-item {
margin-bottom: 20px;
}
.el-dialog .el-textarea {
width: 100%;
}
.basic-information {
background-color: rgb(248, 248, 249) !important;
}
</style>

View File

@@ -0,0 +1,80 @@
<template>
<folder-page type="case" @click="folderHandleSelected" :style="{ height: '600px' }">
<el-table v-loading="loading" :data="dataList" @selection-change="handleSelectionChange">
<el-table-column type="selection"/>
<el-table-column label="用例名称" align="center" prop="name"/>
<el-table-column label="创建人" align="center" prop="createBy"/>
<el-table-column label="用例状态" align="center" prop="status" :formatter="row => ['','草稿', '通过', '不通过'][row.status]"/>
<el-table-column label="创建时间" align="center" prop="createTime"/>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList"/>
</folder-page>
</template>
<script>
import FolderPage from "@/components/FolderPage/index.vue";
import {runCaseSync} from "@/api/test/case";
import {getRelateCaseList} from "@/api/test/planCase";
export default {
name: "relateCase",
components: {FolderPage},
props: {
form: {
type: Object
}
},
data() {
return {
loading: false,
open: false,
showSearch: true,
total: 0,
dataList: [],
queryParams: {
pageNum: 1,
pageSize: 10,
groupId: null,
planId: '',
type: 0,
},
selectedRows: [], // 存储选中的行
caseSID: null,
}
},
methods: {
getSelectedCaseIds() {
// 返回选中的行的用例 id 数组
return this.selectedRows.map(row => row.id);
},
handleSelectionChange(selection) {
this.selectedRows = selection; // 更新选中的行
},
folderHandleSelected(id) {
if (id) {
this.queryParams.groupId = id;
this.getList();
} else {
this.dataList = [];
}
},
getList() {
this.loading = true;
this.queryParams.type = this.form.type;
this.queryParams.planId = this.form.planId;
getRelateCaseList(this.queryParams).then(response => {
this.dataList = response.rows;
this.total = response.total;
this.loading = false;
});
}
}
}
</script>
<style scoped lang="scss">
::v-deep .el-table__row {
cursor: pointer;
}
</style>

View File

@@ -255,6 +255,8 @@
<el-table-column prop="defectNum" label="缺陷数" align="left"/>
<el-table-column label="操作" align="left" fixed="right">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-view" @click.native.stop="handlePlanRun(scope.row)">计划执行
</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click.native.stop="handleDelete(scope.row.id)">删除
</el-button>
</template>
@@ -478,6 +480,9 @@ export default {
this.$modal.msgSuccess("删除成功");
});
},
handlePlanRun(row) {
this.$tab.openPage(`计划执行[${row.name}]`, "/testplan/execute", {id: row.id, name: row.name});
},
editSubmitForm() {
const form = {
...this.editForm,