feat(测试计划): 测试计划批量执行方法开发

This commit is contained in:
Jianguo-Genius 2024-06-06 18:51:18 +08:00 committed by 刘瑞斌
parent f5ae0cd2a2
commit 791432f490
35 changed files with 502 additions and 161 deletions

View File

@ -27,17 +27,23 @@ public class TestPlanConfig implements Serializable {
@NotNull(message = "{test_plan_config.repeat_case.not_blank}", groups = {Created.class})
private Boolean repeatCase;
@Schema(description = "测试计划通过阈值; 0-100, 保留两位小数", requiredMode = Schema.RequiredMode.REQUIRED)
@Schema(description = "", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "{test_plan_config.pass_threshold.not_blank}", groups = {Created.class})
private Double passThreshold;
@Schema(description = "不同用例之间的执行方式(串行/并行)", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{test_plan_config.case_run_mode.not_blank}", groups = {Created.class})
@Size(min = 1, max = 50, message = "{test_plan_config.case_run_mode.length_range}", groups = {Created.class, Updated.class})
private String caseRunMode;
private static final long serialVersionUID = 1L;
public enum Column {
testPlanId("test_plan_id", "testPlanId", "VARCHAR", false),
automaticStatusUpdate("automatic_status_update", "automaticStatusUpdate", "BIT", false),
repeatCase("repeat_case", "repeatCase", "BIT", false),
passThreshold("pass_threshold", "passThreshold", "DECIMAL", false);
passThreshold("pass_threshold", "passThreshold", "DECIMAL", false),
caseRunMode("case_run_mode", "caseRunMode", "VARCHAR", false);
private static final String BEGINNING_DELIMITER = "`";

View File

@ -354,6 +354,76 @@ public class TestPlanConfigExample {
addCriterion("pass_threshold not between", value1, value2, "passThreshold");
return (Criteria) this;
}
public Criteria andCaseRunModeIsNull() {
addCriterion("case_run_mode is null");
return (Criteria) this;
}
public Criteria andCaseRunModeIsNotNull() {
addCriterion("case_run_mode is not null");
return (Criteria) this;
}
public Criteria andCaseRunModeEqualTo(String value) {
addCriterion("case_run_mode =", value, "caseRunMode");
return (Criteria) this;
}
public Criteria andCaseRunModeNotEqualTo(String value) {
addCriterion("case_run_mode <>", value, "caseRunMode");
return (Criteria) this;
}
public Criteria andCaseRunModeGreaterThan(String value) {
addCriterion("case_run_mode >", value, "caseRunMode");
return (Criteria) this;
}
public Criteria andCaseRunModeGreaterThanOrEqualTo(String value) {
addCriterion("case_run_mode >=", value, "caseRunMode");
return (Criteria) this;
}
public Criteria andCaseRunModeLessThan(String value) {
addCriterion("case_run_mode <", value, "caseRunMode");
return (Criteria) this;
}
public Criteria andCaseRunModeLessThanOrEqualTo(String value) {
addCriterion("case_run_mode <=", value, "caseRunMode");
return (Criteria) this;
}
public Criteria andCaseRunModeLike(String value) {
addCriterion("case_run_mode like", value, "caseRunMode");
return (Criteria) this;
}
public Criteria andCaseRunModeNotLike(String value) {
addCriterion("case_run_mode not like", value, "caseRunMode");
return (Criteria) this;
}
public Criteria andCaseRunModeIn(List<String> values) {
addCriterion("case_run_mode in", values, "caseRunMode");
return (Criteria) this;
}
public Criteria andCaseRunModeNotIn(List<String> values) {
addCriterion("case_run_mode not in", values, "caseRunMode");
return (Criteria) this;
}
public Criteria andCaseRunModeBetween(String value1, String value2) {
addCriterion("case_run_mode between", value1, value2, "caseRunMode");
return (Criteria) this;
}
public Criteria andCaseRunModeNotBetween(String value1, String value2) {
addCriterion("case_run_mode not between", value1, value2, "caseRunMode");
return (Criteria) this;
}
}
public static class Criteria extends GeneratedCriteria {

View File

@ -6,6 +6,7 @@
<result column="automatic_status_update" jdbcType="BIT" property="automaticStatusUpdate" />
<result column="repeat_case" jdbcType="BIT" property="repeatCase" />
<result column="pass_threshold" jdbcType="DECIMAL" property="passThreshold" />
<result column="case_run_mode" jdbcType="VARCHAR" property="caseRunMode" />
</resultMap>
<sql id="Example_Where_Clause">
<where>
@ -66,7 +67,7 @@
</where>
</sql>
<sql id="Base_Column_List">
test_plan_id, automatic_status_update, repeat_case, pass_threshold
test_plan_id, automatic_status_update, repeat_case, pass_threshold, case_run_mode
</sql>
<select id="selectByExample" parameterType="io.metersphere.plan.domain.TestPlanConfigExample" resultMap="BaseResultMap">
select
@ -100,9 +101,9 @@
</delete>
<insert id="insert" parameterType="io.metersphere.plan.domain.TestPlanConfig">
insert into test_plan_config (test_plan_id, automatic_status_update, repeat_case,
pass_threshold)
pass_threshold, case_run_mode)
values (#{testPlanId,jdbcType=VARCHAR}, #{automaticStatusUpdate,jdbcType=BIT}, #{repeatCase,jdbcType=BIT},
#{passThreshold,jdbcType=DECIMAL})
#{passThreshold,jdbcType=DECIMAL}, #{caseRunMode,jdbcType=VARCHAR})
</insert>
<insert id="insertSelective" parameterType="io.metersphere.plan.domain.TestPlanConfig">
insert into test_plan_config
@ -119,6 +120,9 @@
<if test="passThreshold != null">
pass_threshold,
</if>
<if test="caseRunMode != null">
case_run_mode,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="testPlanId != null">
@ -133,6 +137,9 @@
<if test="passThreshold != null">
#{passThreshold,jdbcType=DECIMAL},
</if>
<if test="caseRunMode != null">
#{caseRunMode,jdbcType=VARCHAR},
</if>
</trim>
</insert>
<select id="countByExample" parameterType="io.metersphere.plan.domain.TestPlanConfigExample" resultType="java.lang.Long">
@ -156,6 +163,9 @@
<if test="record.passThreshold != null">
pass_threshold = #{record.passThreshold,jdbcType=DECIMAL},
</if>
<if test="record.caseRunMode != null">
case_run_mode = #{record.caseRunMode,jdbcType=VARCHAR},
</if>
</set>
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
@ -166,7 +176,8 @@
set test_plan_id = #{record.testPlanId,jdbcType=VARCHAR},
automatic_status_update = #{record.automaticStatusUpdate,jdbcType=BIT},
repeat_case = #{record.repeatCase,jdbcType=BIT},
pass_threshold = #{record.passThreshold,jdbcType=DECIMAL}
pass_threshold = #{record.passThreshold,jdbcType=DECIMAL},
case_run_mode = #{record.caseRunMode,jdbcType=VARCHAR}
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
@ -183,6 +194,9 @@
<if test="passThreshold != null">
pass_threshold = #{passThreshold,jdbcType=DECIMAL},
</if>
<if test="caseRunMode != null">
case_run_mode = #{caseRunMode,jdbcType=VARCHAR},
</if>
</set>
where test_plan_id = #{testPlanId,jdbcType=VARCHAR}
</update>
@ -190,16 +204,19 @@
update test_plan_config
set automatic_status_update = #{automaticStatusUpdate,jdbcType=BIT},
repeat_case = #{repeatCase,jdbcType=BIT},
pass_threshold = #{passThreshold,jdbcType=DECIMAL}
pass_threshold = #{passThreshold,jdbcType=DECIMAL},
case_run_mode = #{caseRunMode,jdbcType=VARCHAR}
where test_plan_id = #{testPlanId,jdbcType=VARCHAR}
</update>
<insert id="batchInsert" parameterType="map">
insert into test_plan_config
(test_plan_id, automatic_status_update, repeat_case, pass_threshold)
(test_plan_id, automatic_status_update, repeat_case, pass_threshold, case_run_mode
)
values
<foreach collection="list" item="item" separator=",">
(#{item.testPlanId,jdbcType=VARCHAR}, #{item.automaticStatusUpdate,jdbcType=BIT},
#{item.repeatCase,jdbcType=BIT}, #{item.passThreshold,jdbcType=DECIMAL})
#{item.repeatCase,jdbcType=BIT}, #{item.passThreshold,jdbcType=DECIMAL}, #{item.caseRunMode,jdbcType=VARCHAR}
)
</foreach>
</insert>
<insert id="batchInsertSelective" parameterType="map">
@ -224,6 +241,9 @@
<if test="'pass_threshold'.toString() == column.value">
#{item.passThreshold,jdbcType=DECIMAL}
</if>
<if test="'case_run_mode'.toString() == column.value">
#{item.caseRunMode,jdbcType=VARCHAR}
</if>
</foreach>
)
</foreach>

View File

@ -159,8 +159,12 @@ ALTER TABLE api_scenario_report DROP INDEX idx_test_plan_id;
CREATE INDEX idx_test_plan_scenario_id ON api_scenario_report(test_plan_scenario_id);
ALTER TABLE api_scenario_report ADD COLUMN `plan` BIT(1) NOT NULL DEFAULT 0 COMMENT '是否是测试计划整体执行';
CREATE INDEX idx_plan ON api_scenario_report(`plan`);
-- 测试计划配置 增加运行模式
ALTER table test_plan_config
ADD COLUMN `case_run_mode` VARCHAR(50) NOT NULL DEFAULT 'PARALLEL' COMMENT '不同用例之间的执行方式(串行/并行)';
-- set innodb lock wait timeout to default
SET SESSION innodb_lock_wait_timeout = DEFAULT;

View File

@ -27,5 +27,5 @@ public enum ApiExecuteRunMode {
/**
* 定时任务
*/
SCENARIO
SCHEDULE
}

View File

@ -1,13 +1,23 @@
package io.metersphere.sdk.dto.queue;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TestPlanExecutionQueue {
private String queueId;
private String parentQueueId;
private String testPlanId;
//顺序
private long pos;
//执行模式
private String runMode;
//执行来源
private String executionSource;
//预生成的报告ID (不一定会用到)
private String prepareReportId;
private String createUser;
private long createTime;

View File

@ -101,6 +101,7 @@ log.test_plan.api_scenario=接口场景
test_plan.type.not_blank=测试计划类型不能为空
test_plan.group.not_plan=当前测试计划组没有可归档计划
test_plan.group.error=不是合法的测试计划组
test_plan.error=测试计划不合法
test_plan_group.batch.log={0}测试计划组
test_plan.batch.log={0}测试计划
test_plan_report_not_exist=测试计划报告不存在

View File

@ -103,6 +103,7 @@ log.test_plan.api_scenario=Api scenario
test_plan.type.not_blank=Test plan type cannot be empty
test_plan.group.not_plan=There are no archived plans in the current testing plan group
test_plan.group.error=Test plan group error
test_plan.error=Test plan error
test_plan_group.batch.log={0} test plan group
test_plan.batch.log={0} plan
test_plan_report_not_exist=The test plan report does not exist

View File

@ -103,6 +103,7 @@ log.test_plan.api_scenario=接口场景
test_plan.type.not_blank=测试计划类型不能为空
test_plan.group.not_plan=当前测试计划组没有可归档计划
test_plan.group.error=不是合法的测试计划组
test_plan.error=测试计划不合法
test_plan_group.batch.log={0}测试计划组
test_plan.batch.log={0}测试计划
test_plan_report_not_exist=测试计划报告不存在

View File

@ -102,6 +102,7 @@ log.test_plan.api_scenario=接口場景
test_plan.type.not_blank=測試計劃類型不能為空
test_plan.group.not_plan=當前測試計劃組沒有可歸檔計劃
test_plan.group.error=不是合法的測試計劃組
test_plan.error=測試計劃不合法
test_plan_group.batch.log={0}測試計劃組
test_plan.batch.log={0}測試計劃
test_plan_report_not_exist=測試計劃報告不存在

View File

@ -58,7 +58,7 @@ public class ApiScenarioScheduleJob extends BaseScheduleJob {
ApiResourceRunRequest runRequest = apiScenarioRunService.getApiResourceRunRequest(msScenario, tmpParam);
TaskRequestDTO taskRequest = apiScenarioRunService.getTaskRequest(IDGenerator.nextStr(), apiScenarioDetail.getId(), apiScenarioDetail.getProjectId(), ApiExecuteRunMode.SCENARIO.name());
TaskRequestDTO taskRequest = apiScenarioRunService.getTaskRequest(IDGenerator.nextStr(), apiScenarioDetail.getId(), apiScenarioDetail.getProjectId(), ApiExecuteRunMode.SCHEDULE.name());
TaskInfo taskInfo = taskRequest.getTaskInfo();
TaskItem taskItem = taskRequest.getTaskItem();
taskInfo.getRunModeConfig().setPoolId(apiRunModeConfigDTO.getPoolId());

View File

@ -835,7 +835,7 @@ public class ApiDefinitionModuleControllerTests extends BaseTest {
planConfig.setRepeatCase(false);
planConfig.setAutomaticStatusUpdate(false);
request.setTestPlanId("wx_123");
testPlanConfigMapper.insert(planConfig);
testPlanConfigMapper.insertSelective(planConfig);
this.requestPostWithOkAndReturn(URL_FILE_MODULE_COUNT, request);
}

View File

@ -520,7 +520,7 @@ public class FunctionalCaseControllerTests extends BaseTest {
Assertions.assertTrue(moduleCount.containsKey("all"));
//不开启用例重复的测试计划入库再次调用
testPlanConfigMapper.insert(testPlanConfig);
testPlanConfigMapper.insertSelective(testPlanConfig);
mvcResult = this.requestPostWithOkAndReturn(FUNCTIONAL_CASE_MODULE_COUNT, request);
returnData = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);

View File

@ -113,13 +113,25 @@ public class ScheduleService {
}
}
public void closeIfExist(String resourceId, JobKey jobKey, TriggerKey triggerKey, Class clazz) {
public void updateIfExist(String resourceId, boolean enable, JobKey jobKey, TriggerKey triggerKey, Class clazz, String operator) {
ScheduleExample example = new ScheduleExample();
example.createCriteria().andResourceIdEqualTo(resourceId).andJobEqualTo(clazz.getName());
Schedule updateSchedule = new Schedule();
updateSchedule.setEnable(false);
scheduleMapper.updateByExampleSelective(updateSchedule, example);
scheduleManager.removeJob(jobKey, triggerKey);
List<Schedule> scheduleList = scheduleMapper.selectByExample(example);
if (CollectionUtils.isNotEmpty(scheduleList)) {
Schedule schedule = scheduleList.getFirst();
if (!schedule.getEnable().equals(enable)) {
schedule.setEnable(enable);
schedule.setUpdateTime(System.currentTimeMillis());
scheduleMapper.updateByExampleSelective(schedule, example);
apiScheduleNoticeService.sendScheduleNotice(schedule, operator);
if (enable) {
scheduleManager.addCronJob(jobKey, triggerKey, clazz, schedule.getValue(),
scheduleManager.getDefaultJobDataMap(schedule, schedule.getValue(), schedule.getCreateUser()));
} else {
scheduleManager.removeJob(jobKey, triggerKey);
}
}
}
}
public String scheduleConfig(ScheduleConfig scheduleConfig, JobKey jobKey, TriggerKey triggerKey, Class clazz, String operator) {

View File

@ -1,6 +1,7 @@
package io.metersphere.plan.controller;
import io.metersphere.plan.constants.TestPlanResourceConfig;
import io.metersphere.plan.dto.request.TestPlanBatchExecuteRequest;
import io.metersphere.plan.dto.request.TestPlanExecuteRequest;
import io.metersphere.plan.service.TestPlanExecuteService;
import io.metersphere.plan.service.TestPlanLogService;
@ -33,13 +34,23 @@ public class TestPlanExecuteController {
@Resource
private TestPlanExecuteService testPlanExecuteService;
@PostMapping("/start")
@Operation(summary = "测试计划-开始自")
@RequiresPermissions(PermissionConstants.TEST_PLAN_READ_UPDATE)
@CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project")
@Log(type = OperationLogType.EXECUTE, expression = "#msClass.batchEditLog(#request)", msClass = TestPlanLogService.class)
@PostMapping("/single")
@Operation(summary = "测试计划单独执")
@RequiresPermissions(PermissionConstants.TEST_PLAN_READ_EXECUTE)
@CheckOwner(resourceId = "#request.getExecuteId()", resourceType = "test_plan")
@Log(type = OperationLogType.EXECUTE, expression = "#msClass.executeLog(#request)", msClass = TestPlanLogService.class)
public void startExecute(@Validated @RequestBody TestPlanExecuteRequest request) {
testPlanManagementService.checkModuleIsOpen(request.getExecuteId(), TestPlanResourceConfig.CONFIG_TEST_PLAN, Collections.singletonList(TestPlanResourceConfig.CONFIG_TEST_PLAN));
testPlanExecuteService.singleExecuteTestPlan(request, SessionUtils.getUserId());
}
@PostMapping("/batch")
@Operation(summary = "测试计划-开始自行")
@RequiresPermissions(PermissionConstants.TEST_PLAN_READ_EXECUTE)
@CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project")
@Log(type = OperationLogType.EXECUTE, expression = "#msClass.batchExecuteLog(#request)", msClass = TestPlanLogService.class)
public void startExecute(@Validated @RequestBody TestPlanBatchExecuteRequest request) {
testPlanManagementService.checkModuleIsOpen(request.getProjectId(), TestPlanResourceConfig.CHECK_TYPE_PROJECT, Collections.singletonList(TestPlanResourceConfig.CONFIG_TEST_PLAN));
testPlanExecuteService.execute(request, SessionUtils.getUserId());
testPlanExecuteService.batchExecuteTestPlan(request, SessionUtils.getUserId());
}
}

View File

@ -17,4 +17,9 @@ public class TestPlanBatchEditRequest extends TestPlanBatchProcessRequest {
@Schema(description = "标签")
private List<String> tags;
@Schema(description = "定时任务是否开启")
private boolean scheduleOpen;
@Schema(description = "本次编辑的字段", allowableValues = {"TAGS", "SCHEDULE"})
private String editColumn;
}

View File

@ -0,0 +1,29 @@
package io.metersphere.plan.dto.request;
import io.metersphere.sdk.constants.ApiBatchRunMode;
import io.metersphere.sdk.constants.ApiExecuteRunMode;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import java.util.List;
@Data
public class TestPlanBatchExecuteRequest {
@Schema(description = "项目ID")
@NotBlank(message = "project_is_not_exist")
private String projectId;
@Schema(description = "执行ID")
@NotEmpty(message = "test_plan.not.exist")
private List<String> executeIds;
@Schema(description = "执行模式", allowableValues = {"SERIAL", "PARALLEL"}, requiredMode = Schema.RequiredMode.REQUIRED)
private String runMode = ApiBatchRunMode.SERIAL.name();
@Schema(description = "执行来源", allowableValues = {"JENKINS", "SCENARIO", "RUN"}, requiredMode = Schema.RequiredMode.REQUIRED)
private String executionSource = ApiExecuteRunMode.RUN.name();
}

View File

@ -1,26 +1,22 @@
package io.metersphere.plan.dto.request;
import io.metersphere.sdk.constants.ApiBatchRunMode;
import io.metersphere.sdk.constants.ApiExecuteRunMode;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import java.util.List;
/**
* @author wx
*/
@Data
public class TestPlanExecuteRequest {
@Schema(description = "项目ID", required = true)
@NotBlank(message = "{project.id.not_blank}")
private String projectId;
@Schema(description = "执行ID")
List<String> executeIds;
@NotBlank(message = "test_plan.not.exist")
private String executeId;
@Schema(description = "执行模式", allowableValues = {"SERIAL", "PARALLEL"}, requiredMode = Schema.RequiredMode.REQUIRED)
private String executeMode = ApiBatchRunMode.SERIAL.name();
private String runMode = ApiBatchRunMode.SERIAL.name();
@Schema(description = "执行来源", allowableValues = {"JENKINS", "SCENARIO", "RUN"}, requiredMode = Schema.RequiredMode.REQUIRED)
private String executionSource = ApiExecuteRunMode.RUN.name();
}

View File

@ -7,6 +7,9 @@ import java.util.List;
@Data
public class TestPlanResponse extends TestPlanStatisticsResponse {
@Schema(description = "父Id")
private String parent;
@Schema(description = "项目ID")
private String projectId;
@Schema(description = "测试计划编号")

View File

@ -0,0 +1,8 @@
package io.metersphere.plan.enums;
/**
* 测试计划执行队列类型
*/
public enum TestPlanExecuteQueueType {
TEST_PLAN, TEST_PLAN_GROUP, TEST_COLLECTION
}

View File

@ -3,6 +3,7 @@ package io.metersphere.plan.job;
import io.metersphere.plan.dto.request.TestPlanExecuteRequest;
import io.metersphere.plan.service.TestPlanExecuteService;
import io.metersphere.sdk.constants.ApiBatchRunMode;
import io.metersphere.sdk.constants.ApiExecuteRunMode;
import io.metersphere.sdk.util.CommonBeanFactory;
import io.metersphere.sdk.util.JSON;
import io.metersphere.system.schedule.BaseScheduleJob;
@ -10,7 +11,6 @@ import org.quartz.JobExecutionContext;
import org.quartz.JobKey;
import org.quartz.TriggerKey;
import java.util.Collections;
import java.util.Map;
public class TestPlanScheduleJob extends BaseScheduleJob {
@ -21,9 +21,10 @@ public class TestPlanScheduleJob extends BaseScheduleJob {
assert testPlanExecuteService != null;
Map<String, String> runConfig = JSON.parseObject(context.getJobDetail().getJobDataMap().get("config").toString(), Map.class);
String runMode = runConfig.containsKey("runMode") ? runConfig.get("runMode") : ApiBatchRunMode.SERIAL.name();
testPlanExecuteService.execute(new TestPlanExecuteRequest() {{
this.setExecuteIds(Collections.singletonList(resourceId));
this.setExecuteMode(runMode);
testPlanExecuteService.singleExecuteTestPlan(new TestPlanExecuteRequest() {{
this.setExecuteId(resourceId);
this.setRunMode(runMode);
this.setExecutionSource(ApiExecuteRunMode.SCHEDULE.name());
}}, userId);
}

View File

@ -15,7 +15,7 @@ public interface ExtTestPlanApiScenarioMapper {
List<String> selectIdByTestPlanIdOrderByPos(String testPlanId);
Long getMaxPosByTestPlanId(String testPlanId);
Long getMaxPosByRangeId(String testPlanId);
List<String> getIdByParam(ResourceSelectParam resourceSelectParam);

View File

@ -13,10 +13,10 @@
WHERE test_plan_id = #{testPlanId}
ORDER BY pos ASC
</select>
<select id="getMaxPosByTestPlanId" resultType="java.lang.Long">
<select id="getMaxPosByRangeId" resultType="java.lang.Long">
SELECT max(pos)
FROM test_plan_api_scenario
WHERE test_plan_id = #{0}
WHERE test_plan_collection_Id = #{0}
</select>
<select id="getIdByParam"
parameterType="io.metersphere.plan.dto.ResourceSelectParam"

View File

@ -71,6 +71,7 @@
t.type,
t.description,
t.pos,
t.group_id AS parent,
t.tags
FROM test_plan t
INNER JOIN user createUser ON t.create_user = createUser.id

View File

@ -12,29 +12,31 @@ import io.metersphere.functional.dto.FunctionalCaseModuleCountDTO;
import io.metersphere.functional.dto.ProjectOptionDTO;
import io.metersphere.plan.constants.AssociateCaseType;
import io.metersphere.plan.constants.TreeTypeEnums;
import io.metersphere.plan.domain.TestPlanApiCase;
import io.metersphere.plan.domain.TestPlanApiCaseExample;
import io.metersphere.plan.domain.TestPlanCollection;
import io.metersphere.plan.domain.TestPlanCollectionExample;
import io.metersphere.plan.domain.*;
import io.metersphere.plan.dto.ApiCaseModuleDTO;
import io.metersphere.plan.dto.ResourceLogInsertModule;
import io.metersphere.plan.dto.TestPlanCaseRunResultCount;
import io.metersphere.plan.dto.TestPlanCollectionDTO;
import io.metersphere.plan.dto.TestPlanResourceAssociationParam;
import io.metersphere.plan.dto.request.*;
import io.metersphere.plan.dto.response.TestPlanApiCasePageResponse;
import io.metersphere.plan.dto.response.TestPlanAssociationResponse;
import io.metersphere.plan.dto.response.TestPlanOperationResponse;
import io.metersphere.plan.mapper.ExtTestPlanApiCaseMapper;
import io.metersphere.plan.mapper.TestPlanApiCaseMapper;
import io.metersphere.plan.mapper.TestPlanCollectionMapper;
import io.metersphere.plan.mapper.TestPlanMapper;
import io.metersphere.project.domain.Project;
import io.metersphere.project.domain.ProjectExample;
import io.metersphere.project.dto.ModuleCountDTO;
import io.metersphere.project.dto.MoveNodeSortDTO;
import io.metersphere.project.mapper.ProjectMapper;
import io.metersphere.sdk.constants.CaseType;
import io.metersphere.sdk.constants.ModuleConstants;
import io.metersphere.sdk.constants.TestPlanResourceConstants;
import io.metersphere.sdk.domain.Environment;
import io.metersphere.sdk.domain.EnvironmentExample;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.mapper.EnvironmentMapper;
import io.metersphere.sdk.util.BeanUtils;
import io.metersphere.sdk.util.Translator;
@ -63,6 +65,9 @@ import java.util.stream.Collectors;
@Service
@Transactional(rollbackFor = Exception.class)
public class TestPlanApiCaseService extends TestPlanResourceService {
@Resource
private TestPlanMapper testPlanMapper;
@Resource
private TestPlanApiCaseMapper testPlanApiCaseMapper;
@Resource
@ -86,6 +91,8 @@ public class TestPlanApiCaseService extends TestPlanResourceService {
private TestPlanCollectionMapper testPlanCollectionMapper;
@Resource
private ApiTestCaseMapper apiTestCaseMapper;
@Resource
private TestPlanResourceLogService testPlanResourceLogService;
@Override
public void deleteBatchByTestPlanId(List<String> testPlanIdList) {
@ -106,8 +113,7 @@ public class TestPlanApiCaseService extends TestPlanResourceService {
@Override
public void updatePos(String id, long pos) {
// todo
// extTestPlanApiCaseMapper.updatePos(id, pos);
extTestPlanApiCaseMapper.updatePos(id, pos);
}
@Override
@ -142,15 +148,14 @@ public class TestPlanApiCaseService extends TestPlanResourceService {
@Override
public void refreshPos(String testPlanId) {
// todo
// List<String> caseIdList = extTestPlanApiCaseMapper.selectIdByTestPlanIdOrderByPos(testPlanId);
// SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
// ExtTestPlanApiCaseMapper batchUpdateMapper = sqlSession.getMapper(ExtTestPlanApiCaseMapper.class);
// for (int i = 0; i < caseIdList.size(); i++) {
// batchUpdateMapper.updatePos(caseIdList.get(i), i * DEFAULT_NODE_INTERVAL_POS);
// }
// sqlSession.flushStatements();
// SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory);
List<String> caseIdList = extTestPlanApiCaseMapper.selectIdByTestPlanIdOrderByPos(testPlanId);
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
ExtTestPlanApiCaseMapper batchUpdateMapper = sqlSession.getMapper(ExtTestPlanApiCaseMapper.class);
for (int i = 0; i < caseIdList.size(); i++) {
batchUpdateMapper.updatePos(caseIdList.get(i), i * DEFAULT_NODE_INTERVAL_POS);
}
sqlSession.flushStatements();
SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory);
}
/**
@ -541,4 +546,23 @@ public class TestPlanApiCaseService extends TestPlanResourceService {
apiCaseExample.createCriteria().andTestPlanIdEqualTo(planId);
apiBatchMapper.updateByExampleSelective(record, apiCaseExample);
}
public TestPlanOperationResponse sortNode(ResourceSortRequest request, LogInsertModule logInsertModule) {
TestPlanApiCase dragNode = testPlanApiCaseMapper.selectByPrimaryKey(request.getMoveId());
TestPlan testPlan = testPlanMapper.selectByPrimaryKey(request.getTestCollectionId());
if (dragNode == null) {
throw new MSException(Translator.get("test_plan.drag.node.error"));
}
TestPlanOperationResponse response = new TestPlanOperationResponse();
MoveNodeSortDTO sortDTO = super.getNodeSortDTO(
request.getTestCollectionId(),
super.getNodeMoveRequest(request, true),
extTestPlanApiCaseMapper::selectDragInfoById,
extTestPlanApiCaseMapper::selectNodeByPosOperator
);
super.sort(sortDTO);
response.setOperationCount(1);
testPlanResourceLogService.saveSortLog(testPlan, request.getMoveId(), new ResourceLogInsertModule(TestPlanResourceConstants.RESOURCE_API_CASE, logInsertModule));
return response;
}
}

View File

@ -3,20 +3,26 @@ package io.metersphere.plan.service;
import io.metersphere.api.dto.scenario.ApiScenarioDTO;
import io.metersphere.api.service.scenario.ApiScenarioService;
import io.metersphere.plan.constants.AssociateCaseType;
import io.metersphere.plan.domain.TestPlan;
import io.metersphere.plan.domain.TestPlanApiScenario;
import io.metersphere.plan.domain.TestPlanApiScenarioExample;
import io.metersphere.plan.dto.ResourceLogInsertModule;
import io.metersphere.plan.dto.TestPlanCaseRunResultCount;
import io.metersphere.plan.dto.TestPlanCollectionDTO;
import io.metersphere.plan.dto.request.BaseCollectionAssociateRequest;
import io.metersphere.plan.dto.request.ResourceSortRequest;
import io.metersphere.plan.dto.request.TestPlanApiScenarioRequest;
import io.metersphere.plan.mapper.ExtTestPlanApiScenarioMapper;
import io.metersphere.plan.mapper.TestPlanApiScenarioMapper;
import io.metersphere.plan.mapper.TestPlanCollectionMapper;
import io.metersphere.plan.dto.response.TestPlanOperationResponse;
import io.metersphere.plan.mapper.*;
import io.metersphere.project.dto.MoveNodeSortDTO;
import io.metersphere.sdk.constants.CaseType;
import io.metersphere.sdk.constants.TestPlanResourceConstants;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.util.BeanUtils;
import io.metersphere.sdk.util.Translator;
import io.metersphere.system.dto.LogInsertModule;
import io.metersphere.system.uid.IDGenerator;
import jakarta.annotation.Resource;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
@ -43,6 +49,10 @@ public class TestPlanApiScenarioService extends TestPlanResourceService {
private TestPlanCollectionMapper testPlanCollectionMapper;
@Resource
private ApiScenarioService apiScenarioService;
@Resource
private TestPlanMapper testPlanMapper;
@Resource
private TestPlanResourceLogService testPlanResourceLogService;
@Override
public void deleteBatchByTestPlanId(List<String> testPlanIdList) {
@ -52,14 +62,18 @@ public class TestPlanApiScenarioService extends TestPlanResourceService {
}
@Override
public long getNextOrder(String projectId) {
return 0;
public long getNextOrder(String collectionId) {
Long maxPos = extTestPlanApiScenarioMapper.getMaxPosByRangeId(collectionId);
if (maxPos == null) {
return 0;
} else {
return maxPos + DEFAULT_NODE_INTERVAL_POS;
}
}
@Override
public void updatePos(String id, long pos) {
// todo
// extTestPlanApiScenarioMapper.updatePos(id, pos);
extTestPlanApiScenarioMapper.updatePos(id, pos);
}
@Override
@ -94,19 +108,18 @@ public class TestPlanApiScenarioService extends TestPlanResourceService {
@Override
public void refreshPos(String testPlanId) {
// todo
// List<String> caseIdList = extTestPlanApiScenarioMapper.selectIdByTestPlanIdOrderByPos(testPlanId);
// SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
// ExtTestPlanApiCaseMapper batchUpdateMapper = sqlSession.getMapper(ExtTestPlanApiCaseMapper.class);
// for (int i = 0; i < caseIdList.size(); i++) {
// batchUpdateMapper.updatePos(caseIdList.get(i), i * DEFAULT_NODE_INTERVAL_POS);
// }
// sqlSession.flushStatements();
// SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory);
List<String> caseIdList = extTestPlanApiScenarioMapper.selectIdByTestPlanIdOrderByPos(testPlanId);
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
ExtTestPlanApiCaseMapper batchUpdateMapper = sqlSession.getMapper(ExtTestPlanApiCaseMapper.class);
for (int i = 0; i < caseIdList.size(); i++) {
batchUpdateMapper.updatePos(caseIdList.get(i), i * DEFAULT_NODE_INTERVAL_POS);
}
sqlSession.flushStatements();
SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory);
}
@Override
public void associateCollection(String planId, Map<String, List<BaseCollectionAssociateRequest>> collectionAssociates, String userId) {
public void associateCollection(String planId, Map<String, List<BaseCollectionAssociateRequest>> collectionAssociates,String userId) {
List<BaseCollectionAssociateRequest> apiScenarios = collectionAssociates.get(AssociateCaseType.API_SCENARIO);
// TODO: 调用具体的关联场景用例入库方法 入参{计划ID, 测试集ID, 关联的用例ID集合}
}
@ -136,4 +149,22 @@ public class TestPlanApiScenarioService extends TestPlanResourceService {
List<ApiScenarioDTO> scenarioPage = apiScenarioService.getScenarioPage(request, isRepeat, request.getTestPlanId());
return scenarioPage;
}
public TestPlanOperationResponse sortNode(ResourceSortRequest request, LogInsertModule logInsertModule) {
TestPlanApiScenario dragNode = testPlanApiScenarioMapper.selectByPrimaryKey(request.getMoveId());
TestPlan testPlan = testPlanMapper.selectByPrimaryKey(request.getTestCollectionId());
if (dragNode == null) {
throw new MSException(Translator.get("test_plan.drag.node.error"));
}
TestPlanOperationResponse response = new TestPlanOperationResponse();
MoveNodeSortDTO sortDTO = super.getNodeSortDTO(
request.getTestCollectionId(),
super.getNodeMoveRequest(request, true),
extTestPlanApiScenarioMapper::selectDragInfoById,
extTestPlanApiScenarioMapper::selectNodeByPosOperator
);
super.sort(sortDTO);
response.setOperationCount(1);
testPlanResourceLogService.saveSortLog(testPlan, request.getMoveId(), new ResourceLogInsertModule(TestPlanResourceConstants.RESOURCE_API_CASE, logInsertModule));
return response;
}
}

View File

@ -191,7 +191,7 @@ public class TestPlanBatchOperationService extends TestPlanBaseUtilsService {
TestPlanConfig newTestPlanConfig = new TestPlanConfig();
BeanUtils.copyBean(newTestPlanConfig, originalTestPlanConfig);
newTestPlanConfig.setTestPlanId(testPlan.getId());
testPlanConfigMapper.insert(newTestPlanConfig);
testPlanConfigMapper.insertSelective(newTestPlanConfig);
}
//todo 测试规划信息

View File

@ -1,10 +1,14 @@
package io.metersphere.plan.service;
import io.metersphere.plan.domain.TestPlan;
import io.metersphere.plan.dto.request.TestPlanBatchExecuteRequest;
import io.metersphere.plan.dto.request.TestPlanExecuteRequest;
import io.metersphere.plan.enums.TestPlanExecuteQueueType;
import io.metersphere.plan.mapper.TestPlanMapper;
import io.metersphere.sdk.constants.ApiBatchRunMode;
import io.metersphere.sdk.constants.TestPlanConstants;
import io.metersphere.sdk.dto.queue.TestPlanExecutionQueue;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.util.JSON;
import io.metersphere.system.uid.IDGenerator;
import jakarta.annotation.Resource;
@ -30,70 +34,137 @@ public class TestPlanExecuteService {
@Resource
private RedisTemplate<String, String> redisTemplate;
public static final String TEST_PLAN_QUEUE_PREFIX = "queue:test-plan:";
public static final String QUEUE_PREFIX_TEST_PLAN = "test-plan-execute:";
public static final String QUEUE_PREFIX_TEST_PLAN_GROUP = "test-plan-group-execute:";
public static final String QUEUE_PREFIX_TEST_COLLECTION = "test-collection-execute:";
private TestPlanExecutionQueue genQueue(String testPlanId, String queueId, long pos, String userId, String executeMode) {
TestPlanExecutionQueue testPlanExecutionQueue = new TestPlanExecutionQueue();
testPlanExecutionQueue.setTestPlanId(testPlanId);
testPlanExecutionQueue.setQueueId(queueId);
testPlanExecutionQueue.setPos(pos);
testPlanExecutionQueue.setPrepareReportId(IDGenerator.nextStr());
testPlanExecutionQueue.setCreateUser(userId);
testPlanExecutionQueue.setCreateTime(System.currentTimeMillis());
testPlanExecutionQueue.setRunMode(executeMode);
return testPlanExecutionQueue;
public void executeTestPlan(TestPlan testPlan, String executionSource, String userId) {
//todo 查询执行配置,配置下一步的队列
}
public void execute(TestPlanExecuteRequest request, String userId) {
/**
* 预执行执行测试计划
*/
private void prepareExecuteTestPlan(TestPlan testPlan, String parentQueueId, String runMode, String executionSource, String userId) {
if (testPlan == null || StringUtils.equalsIgnoreCase(testPlan.getStatus(), TestPlanConstants.TEST_PLAN_STATUS_ARCHIVED)) {
throw new MSException("test_plan.error");
}
if (StringUtils.equalsIgnoreCase(testPlan.getType(), TestPlanConstants.TEST_PLAN_TYPE_GROUP)) {
List<TestPlan> children = testPlanService.selectNotArchivedChildren(testPlan.getId());
long pos = 0;
List<TestPlanExecutionQueue> childrenQueue = new ArrayList<>();
for (TestPlan child : children) {
childrenQueue.add(
new TestPlanExecutionQueue(child.getGroupId(), parentQueueId, child.getId(), pos++, runMode, executionSource, IDGenerator.nextStr(), userId, System.currentTimeMillis())
);
}
if (StringUtils.equalsIgnoreCase(runMode, ApiBatchRunMode.SERIAL.name())) {
//串行
childrenQueue.forEach(childQueue -> {
redisTemplate.opsForList().rightPush(QUEUE_PREFIX_TEST_PLAN_GROUP + testPlan.getId(), JSON.toJSONString(childrenQueue));
});
executeNextTestPlanByGroupQueueId(testPlan.getId(), parentQueueId);
} else {
//并行
childrenQueue.forEach(childQueue -> {
executeTestPlan(testPlanMapper.selectByPrimaryKey(childQueue.getTestPlanId()), childQueue.getExecutionSource(), childQueue.getCreateUser());
});
}
} else {
this.executeTestPlan(testPlan, executionSource, userId);
}
}
/**
* 单个执行测试计划
*/
public void singleExecuteTestPlan(TestPlanExecuteRequest request, String userId) {
prepareExecuteTestPlan(testPlanMapper.selectByPrimaryKey(request.getExecuteId()), null, request.getRunMode(), request.getExecutionSource(), userId);
}
/**
* 批量执行测试计划
*/
public void batchExecuteTestPlan(TestPlanBatchExecuteRequest request, String userId) {
List<String> rightfulIds = testPlanService.selectRightfulIds(request.getExecuteIds());
if (CollectionUtils.isNotEmpty(rightfulIds)) {
//遍历原始ID只挑选符合条件的ID进行防止顺序错乱
String executeMode = request.getExecuteMode();
String runMode = request.getRunMode();
String queueId = IDGenerator.nextStr();
long pos = 1;
long pos = 0;
List<TestPlanExecutionQueue> testPlanExecutionQueues = new ArrayList<>();
//遍历原始ID只挑选符合条件的ID进行防止顺序错乱
for (String testPlanId : request.getExecuteIds()) {
List<TestPlan> childList = testPlanService.selectChildPlanByGroupId(testPlanId);
if (CollectionUtils.isNotEmpty(childList)) {
for (TestPlan child : childList) {
testPlanExecutionQueues.add(genQueue(child.getId(), queueId, pos++, userId, executeMode));
}
} else {
testPlanExecutionQueues.add(genQueue(testPlanId, queueId, pos++, userId, executeMode));
if (rightfulIds.contains(testPlanId)) {
testPlanExecutionQueues.add(
new TestPlanExecutionQueue(queueId, null, testPlanId, pos++, runMode, request.getExecutionSource(), IDGenerator.nextStr(), userId, System.currentTimeMillis())
);
}
}
if (StringUtils.equalsIgnoreCase(request.getExecuteMode(), ApiBatchRunMode.SERIAL.name())) {
if (StringUtils.equalsIgnoreCase(request.getRunMode(), ApiBatchRunMode.SERIAL.name())) {
//串行
testPlanExecutionQueues.forEach(testPlanExecutionQueue -> {
redisTemplate.opsForList().rightPush(TEST_PLAN_QUEUE_PREFIX + queueId, JSON.toJSONString(testPlanExecutionQueue));
redisTemplate.opsForList().rightPush(QUEUE_PREFIX_TEST_PLAN + queueId, JSON.toJSONString(testPlanExecutionQueue));
});
try {
executeByExecutionQueue(getNextDetail(queueId));
} catch (Exception ignore) {
}
executeNextTestPlanByQueueId(queueId);
} else {
//并行
testPlanExecutionQueues.forEach(testPlanExecutionQueue -> {
executeByExecutionQueue(testPlanExecutionQueue);
prepareExecuteTestPlan(testPlanMapper.selectByPrimaryKey(testPlanExecutionQueue.getTestPlanId()),
null,
testPlanExecutionQueue.getRunMode(),
testPlanExecutionQueue.getExecutionSource(),
testPlanExecutionQueue.getCreateUser());
});
}
}
}
public void executeByExecutionQueue(TestPlanExecutionQueue queue) {
Thread.startVirtualThread(() -> {
TestPlan testPlan = testPlanMapper.selectByPrimaryKey(queue.getTestPlanId());
// todo 获取测试规划通过测试规划执行方式确定用例的执行方式
});
//执行下一个测试计划组节点的测试计划
private void executeNextTestPlanByGroupQueueId(String groupQueueId, String parentQueueId) {
TestPlanExecutionQueue nextQueue = getNextDetail(groupQueueId, QUEUE_PREFIX_TEST_PLAN_GROUP, TestPlanExecutionQueue.class);
if (nextQueue != null) {
try {
executeTestPlan(testPlanMapper.selectByPrimaryKey(nextQueue.getTestPlanId()), nextQueue.getExecutionSource(), nextQueue.getCreateUser());
} catch (Exception e) {
this.executeNextTestPlanByQueueId(groupQueueId);
}
} else {
// todo 测试计划组执行完成
if (StringUtils.isNotEmpty(parentQueueId)) {
// 如果测试计划组是在批量执行时处理的继续进行批量执行队列里的下个节点
executeNextTestPlanByQueueId(parentQueueId);
}
}
}
//执行下一个批量执行节点的测试计划
private void executeNextTestPlanByQueueId(String queueId) {
TestPlanExecutionQueue nextQueue = getNextDetail(queueId, QUEUE_PREFIX_TEST_PLAN, TestPlanExecutionQueue.class);
if (nextQueue != null) {
try {
prepareExecuteTestPlan(
testPlanMapper.selectByPrimaryKey(nextQueue.getTestPlanId()),
queueId,
nextQueue.getRunMode(),
nextQueue.getExecutionSource(),
nextQueue.getCreateUser());
} catch (Exception e) {
this.executeNextTestPlanByQueueId(queueId);
}
}
}
/**
* 获取下一个节点
* 获取下一个队列节点
*/
public TestPlanExecutionQueue getNextDetail(String queueId) throws Exception {
String queueKey = TEST_PLAN_QUEUE_PREFIX + queueId;
private <T> T getNextDetail(String queueId, String queueType, Class<T> formatClass) {
String queueKey = this.genQueueKey(queueId, queueType);
ListOperations<String, String> listOps = redisTemplate.opsForList();
String queueDetail = listOps.leftPop(queueKey);
if (StringUtils.isBlank(queueDetail)) {
@ -103,32 +174,46 @@ public class TestPlanExecuteService {
if (StringUtils.isNotBlank(queueDetail)) {
break;
}
Thread.sleep(1000);
try {
Thread.sleep(1000);
} catch (Exception ignore) {
}
}
}
if (StringUtils.isNotBlank(queueDetail)) {
Long size = size(queueId);
Long size = getQueueSize(queueId);
if (size == null || size == 0) {
// 最后一个节点清理队列
deleteQueue(queueId);
deleteQueue(queueKey);
}
return JSON.parseObject(queueDetail, TestPlanExecutionQueue.class);
return JSON.parseObject(queueDetail, formatClass);
}
// 整体获取完清理队列
deleteQueue(queueId);
deleteQueue(queueKey);
return null;
}
public void deleteQueue(String queueId) {
redisTemplate.delete(TEST_PLAN_QUEUE_PREFIX + queueId);
private void deleteQueue(String queueKey) {
redisTemplate.delete(queueKey);
}
public Long size(String queueId) {
private Long getQueueSize(String queueKey) {
ListOperations<String, String> listOps = redisTemplate.opsForList();
String queueKey = TEST_PLAN_QUEUE_PREFIX + queueId;
return listOps.size(queueKey);
}
//生成队列key
private String genQueueKey(String queueId, String queueType) {
String key = "";
if (StringUtils.equalsIgnoreCase(queueType, TestPlanExecuteQueueType.TEST_PLAN.name())) {
key = QUEUE_PREFIX_TEST_PLAN;
} else if (StringUtils.equalsIgnoreCase(queueType, TestPlanExecuteQueueType.TEST_PLAN_GROUP.name())) {
key = QUEUE_PREFIX_TEST_PLAN_GROUP;
} else if (StringUtils.equalsIgnoreCase(queueType, TestPlanExecuteQueueType.TEST_COLLECTION.name())) {
key = QUEUE_PREFIX_TEST_COLLECTION;
}
return key + queueId;
}
}

View File

@ -142,7 +142,7 @@ public class TestPlanService extends TestPlanBaseUtilsService {
handleAssociateCase(createOrCopyRequest.getBaseAssociateCaseRequest(), operator, createTestPlan);
testPlanMapper.insert(createTestPlan);
testPlanConfigMapper.insert(testPlanConfig);
testPlanConfigMapper.insertSelective(testPlanConfig);
return createTestPlan;
}
@ -307,10 +307,8 @@ public class TestPlanService extends TestPlanBaseUtilsService {
//删除测试计划报告 todo: 正式版增加接口用例报告接口场景报告的清理
testPlanReportService.deleteByTestPlanIds(testPlanIds);
/*
todo
删除计划定时任务
*/
//删除定时任务
scheduleService.deleteByResourceIds(testPlanIds, TestPlanScheduleJob.class.getName());
}
@ -622,12 +620,25 @@ public class TestPlanService extends TestPlanBaseUtilsService {
* @param userId
*/
public void batchEdit(TestPlanBatchEditRequest request, String userId) {
// 目前计划的批量操作不支持全选所有页
List<String> ids = request.getSelectIds();
if (CollectionUtils.isNotEmpty(ids)) {
User user = userMapper.selectByPrimaryKey(userId);
handleTags(request, userId, ids);
testPlanSendNoticeService.batchSendNotice(request.getProjectId(), ids, user, NoticeConstants.Event.UPDATE);
if (StringUtils.equalsIgnoreCase(request.getEditColumn(), "SCHEDULE")) {
TestPlanExample example = new TestPlanExample();
example.createCriteria().andIdIn(ids).andStatusNotEqualTo(TestPlanConstants.TEST_PLAN_STATUS_ARCHIVED);
List<TestPlan> testPlanList = testPlanMapper.selectByExample(example);
//批量编辑定时任务
for (TestPlan testPlan : testPlanList) {
scheduleService.updateIfExist(testPlan.getId(), request.isScheduleOpen(), TestPlanScheduleJob.getJobKey(testPlan.getId()),
TestPlanScheduleJob.getTriggerKey(testPlan.getId()), TestPlanScheduleJob.class, userId);
}
} else {
//默认编辑tags
User user = userMapper.selectByPrimaryKey(userId);
handleTags(request, userId, ids);
testPlanSendNoticeService.batchSendNotice(request.getProjectId(), ids, user, NoticeConstants.Event.UPDATE);
}
}
}
@ -750,7 +761,7 @@ public class TestPlanService extends TestPlanBaseUtilsService {
testPlan.setStatus(testPlanFinalStatus);
testPlanMapper.updateByPrimaryKeySelective(testPlan);
List<TestPlan> childPlan = this.selectChildPlanByGroupId(testPlanId);
List<TestPlan> childPlan = this.selectNotArchivedChildren(testPlanId);
if (CollectionUtils.isNotEmpty(childPlan)) {
TestPlan updateGroupPlan = new TestPlan();
updateGroupPlan.setId(testPlanId);
@ -787,11 +798,11 @@ public class TestPlanService extends TestPlanBaseUtilsService {
if (request.isEnable() && StringUtils.equalsIgnoreCase(testPlan.getType(), TestPlanConstants.TEST_PLAN_TYPE_GROUP)) {
//配置开启的测试计划组定时任务要将组下的所有测试计划定时任务都关闭掉
List<TestPlan> children = this.selectChildPlanByGroupId(testPlan.getId());
List<TestPlan> children = this.selectNotArchivedChildren(testPlan.getId());
for (TestPlan child : children) {
scheduleService.closeIfExist(child.getId(), TestPlanScheduleJob.getJobKey(testPlan.getId()),
scheduleService.updateIfExist(child.getId(), false, TestPlanScheduleJob.getJobKey(testPlan.getId()),
TestPlanScheduleJob.getTriggerKey(testPlan.getId()),
TestPlanScheduleJob.class);
TestPlanScheduleJob.class, operator);
}
}
@ -811,7 +822,7 @@ public class TestPlanService extends TestPlanBaseUtilsService {
return extTestPlanMapper.selectNotArchivedIds(executeIds);
}
public List<TestPlan> selectChildPlanByGroupId(String testPlanGroupId) {
public List<TestPlan> selectNotArchivedChildren(String testPlanGroupId) {
TestPlanExample example = new TestPlanExample();
example.createCriteria().andGroupIdEqualTo(testPlanGroupId).andStatusNotEqualTo(TestPlanConstants.TEST_PLAN_STATUS_ARCHIVED);
example.setOrderByClause("pos asc");

View File

@ -77,7 +77,7 @@
<!-- typeHandler="io.metersphere.handler.ListTypeHandler"/>-->
<!-- </table>-->
<!-- <table tableName="test_plan_functional_case"/>-->
<table tableName="test_plan_config"/>
<!-- <table tableName="test_plan_api_case"/>-->
<!-- <table tableName="test_plan_api_scenario"/>-->
<!-- <table tableName="test_plan_report"/>-->

View File

@ -119,7 +119,8 @@ public class TestPlanTests extends BaseTest {
private static final String URL_POST_TEST_PLAN_BATCH_DELETE = "/test-plan/batch-delete";
private static final String URL_POST_TEST_PLAN_SCHEDULE = "/test-plan/schedule-config";
private static final String URL_POST_TEST_PLAN_SCHEDULE_DELETE = "/test-plan/schedule-config-delete/%s";
private static final String URL_POST_TEST_PLAN_EXECUTE = "/test-plan-execute/start";
private static final String URL_POST_TEST_PLAN_SINGLE_EXECUTE = "/test-plan-execute/single";
private static final String URL_POST_TEST_PLAN_BATCH_EXECUTE = "/test-plan-execute/batch";
//测试计划资源-功能用例
private static final String URL_POST_RESOURCE_CASE_ASSOCIATION = "/test-plan/association";
@ -1492,13 +1493,21 @@ public class TestPlanTests extends BaseTest {
@Order(71)
public void executeTest() throws Exception {
TestPlanExecuteRequest executeRequest = new TestPlanExecuteRequest();
executeRequest.setExecuteIds(Collections.singletonList(groupTestPlanId7));
executeRequest.setProjectId(project.getId());
executeRequest.setExecuteId(groupTestPlanId7);
//串行
this.requestPostWithOk(URL_POST_TEST_PLAN_EXECUTE, executeRequest);
this.requestPostWithOk(URL_POST_TEST_PLAN_SINGLE_EXECUTE, executeRequest);
//并行
executeRequest.setExecuteMode(ApiBatchRunMode.PARALLEL.name());
this.requestPostWithOk(URL_POST_TEST_PLAN_EXECUTE, executeRequest);
executeRequest.setRunMode(ApiBatchRunMode.PARALLEL.name());
this.requestPostWithOk(URL_POST_TEST_PLAN_SINGLE_EXECUTE, executeRequest);
TestPlanBatchExecuteRequest batchExecuteRequest = new TestPlanBatchExecuteRequest();
batchExecuteRequest.setExecuteIds(Collections.singletonList(groupTestPlanId7));
batchExecuteRequest.setProjectId(project.getId());
//串行
this.requestPostWithOk(URL_POST_TEST_PLAN_BATCH_EXECUTE, batchExecuteRequest);
//并行
batchExecuteRequest.setRunMode(ApiBatchRunMode.PARALLEL.name());
this.requestPostWithOk(URL_POST_TEST_PLAN_BATCH_EXECUTE, batchExecuteRequest);
}
@Test
@Order(81)

View File

@ -4,10 +4,10 @@ VALUES
('wxxx_2', 10000, 'wxx_1234', 'NONE', '1', 'eeew', 'PREPARED', 'TEST_PLAN', NULL, 1714980158000, 'WX', 1714980158000, 'WX', 1714980158000, 1714980158000, 1714980158000, 1714980158000, '11');
INSERT INTO `test_plan_config`(`test_plan_id`, `automatic_status_update`, `repeat_case`, `pass_threshold`)
VALUES
('wxxx_1', b'0', b'0', 100),
('wxxx_2', b'0', b'0', 100);
INSERT INTO `test_plan_config`(`test_plan_id`, `automatic_status_update`, `repeat_case`, `pass_threshold`,
`case_run_mode`)
VALUES ('wxxx_1', b'0', b'0', 100, 'PARALLEL'),
('wxxx_2', b'0', b'0', 100, 'PARALLEL');
INSERT INTO `api_definition`(`id`, `name`, `protocol`, `method`, `path`, `status`, `num`, `tags`, `pos`, `project_id`, `module_id`, `latest`, `version_id`, `ref_id`, `description`, `create_time`, `create_user`, `update_time`, `update_user`, `delete_user`, `delete_time`, `deleted`)

View File

@ -4,10 +4,10 @@ VALUES
('wxx_2', 10000, 'wx_1234', 'NONE', '1', 'eeew', 'PREPARED', 'TEST_PLAN', NULL, 1714980158000, 'WX', 1714980158000, 'WX', 1714980158000, 1714980158000, 1714980158000, 1714980158000, '11');
INSERT INTO `test_plan_config`(`test_plan_id`, `automatic_status_update`, `repeat_case`, `pass_threshold`)
VALUES
('wxx_1', b'0', b'0', 100),
('wxx_2', b'0', b'0', 100);
INSERT INTO `test_plan_config`(`test_plan_id`, `automatic_status_update`, `repeat_case`, `pass_threshold`,
'case_run_mode')
VALUES ('wxx_1', b'0', b'0', 100, 'PARALLEL'),
('wxx_2', b'0', b'0', 100, 'PARALLEL');
INSERT INTO `test_plan_functional_case`(`id`, `test_plan_id`, `functional_case_id`, `create_time`, `create_user`, `execute_user`, `last_exec_time`, `last_exec_result`, `pos`, `test_plan_collection_id`)
VALUES ('wxx_tpfc_1', 'wxx_1', 'wxx_test_1', 1714980158000, 'admin', NULL, NULL, NULL, 1, '123');

View File

@ -2,9 +2,10 @@
INSERT INTO `test_plan`(`id`, `num`, `project_id`, `group_id`, `module_id`, `name`, `status`, `type`, `tags`, `create_time`, `create_user`, `update_time`, `update_user`, `planned_start_time`, `planned_end_time`, `actual_start_time`, `actual_end_time`, `description`) VALUES
('plan_id_for_gen_report', 100001, '100001100001', 'NONE', '1', 'gen-report-plan', 'PREPARED', 'TEST_PLAN', NULL, CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, '11'),
('plan_id_for_gen_report_1', 100001, '100001100001', 'NONE', '1', 'gen-report-plan-1', 'PREPARED', 'TEST_PLAN', NULL, CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, 'admin', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, '11');
INSERT INTO `test_plan_config`(`test_plan_id`, `automatic_status_update`, `repeat_case`, `pass_threshold`) VALUES
('plan_id_for_gen_report', b'0', b'0', 100.00),
('plan_id_for_gen_report_1', b'0', b'0', 0.00);
INSERT INTO `test_plan_config`(`test_plan_id`, `automatic_status_update`, `repeat_case`, `pass_threshold`,
'case_run_mode')
VALUES ('plan_id_for_gen_report', b'0', b'0', 100.00, 'PARALLEL'),
('plan_id_for_gen_report_1', b'0', b'0', 0.00, 'PARALLEL');
-- 计划关联用例执行的测试数据
INSERT INTO `test_plan_functional_case` (`id`, `test_plan_id`, `functional_case_id`, `create_time`, `create_user`, `execute_user`, `last_exec_time`, `last_exec_result`, `pos`, `test_plan_collection_id`) VALUES

View File

@ -43,11 +43,11 @@ INSERT INTO `test_plan_module`(`id`, `project_id`, `name`, `parent_id`, `pos`, `
VALUES ('1', 'songtianyang-fix-wx', 'wx_测试模块名称', 'ROOT', 1, 1714980158000, 1714980158000, 'admin', 'admin');
INSERT INTO `test_plan_config`(`test_plan_id`, `automatic_status_update`, `repeat_case`, `pass_threshold`)
VALUES
('wx_test_plan_id_1', b'0', b'0', 100),
('wx_test_plan_id_4', b'0', b'0', 100),
('wx_test_plan_id_7', b'0', b'0', 100);
INSERT INTO `test_plan_config`(`test_plan_id`, `automatic_status_update`, `repeat_case`, `pass_threshold`,
'case_run_mode')
VALUES ('wx_test_plan_id_1', b'0', b'0', 100, 'PARALLEL'),
('wx_test_plan_id_4', b'0', b'0', 100, 'PARALLEL'),
('wx_test_plan_id_7', b'0', b'0', 100, 'PARALLEL');
INSERT INTO functional_case(id, num, module_id, project_id, template_id, name, review_status, tags, case_edit_type, pos, version_id, ref_id, last_execute_result, deleted, public_case, latest, create_user, update_user, delete_user, create_time, update_time, delete_time)