feat(测试计划): 补充计划执行生成报告逻辑

This commit is contained in:
song-cc-rock 2024-06-14 18:15:33 +08:00 committed by Craftsman
parent 5884314653
commit 03737a5cc6
12 changed files with 253 additions and 185 deletions

View File

@ -35,9 +35,6 @@ public class TestPlanReport implements Serializable {
@Schema(description = "创建时间") @Schema(description = "创建时间")
private Long createTime; private Long createTime;
@Schema(description = "执行时间;计划真正执行的时间")
private Long executeTime;
@Schema(description = "开始时间;计划开始执行的时间") @Schema(description = "开始时间;计划开始执行的时间")
private Long startTime; private Long startTime;
@ -93,7 +90,6 @@ public class TestPlanReport implements Serializable {
name("name", "name", "VARCHAR", true), name("name", "name", "VARCHAR", true),
createUser("create_user", "createUser", "VARCHAR", false), createUser("create_user", "createUser", "VARCHAR", false),
createTime("create_time", "createTime", "BIGINT", false), createTime("create_time", "createTime", "BIGINT", false),
executeTime("execute_time", "executeTime", "BIGINT", false),
startTime("start_time", "startTime", "BIGINT", false), startTime("start_time", "startTime", "BIGINT", false),
endTime("end_time", "endTime", "BIGINT", false), endTime("end_time", "endTime", "BIGINT", false),
execStatus("exec_status", "execStatus", "VARCHAR", false), execStatus("exec_status", "execStatus", "VARCHAR", false),

View File

@ -445,66 +445,6 @@ public class TestPlanReportExample {
return (Criteria) this; return (Criteria) this;
} }
public Criteria andExecuteTimeIsNull() {
addCriterion("execute_time is null");
return (Criteria) this;
}
public Criteria andExecuteTimeIsNotNull() {
addCriterion("execute_time is not null");
return (Criteria) this;
}
public Criteria andExecuteTimeEqualTo(Long value) {
addCriterion("execute_time =", value, "executeTime");
return (Criteria) this;
}
public Criteria andExecuteTimeNotEqualTo(Long value) {
addCriterion("execute_time <>", value, "executeTime");
return (Criteria) this;
}
public Criteria andExecuteTimeGreaterThan(Long value) {
addCriterion("execute_time >", value, "executeTime");
return (Criteria) this;
}
public Criteria andExecuteTimeGreaterThanOrEqualTo(Long value) {
addCriterion("execute_time >=", value, "executeTime");
return (Criteria) this;
}
public Criteria andExecuteTimeLessThan(Long value) {
addCriterion("execute_time <", value, "executeTime");
return (Criteria) this;
}
public Criteria andExecuteTimeLessThanOrEqualTo(Long value) {
addCriterion("execute_time <=", value, "executeTime");
return (Criteria) this;
}
public Criteria andExecuteTimeIn(List<Long> values) {
addCriterion("execute_time in", values, "executeTime");
return (Criteria) this;
}
public Criteria andExecuteTimeNotIn(List<Long> values) {
addCriterion("execute_time not in", values, "executeTime");
return (Criteria) this;
}
public Criteria andExecuteTimeBetween(Long value1, Long value2) {
addCriterion("execute_time between", value1, value2, "executeTime");
return (Criteria) this;
}
public Criteria andExecuteTimeNotBetween(Long value1, Long value2) {
addCriterion("execute_time not between", value1, value2, "executeTime");
return (Criteria) this;
}
public Criteria andStartTimeIsNull() { public Criteria andStartTimeIsNull() {
addCriterion("start_time is null"); addCriterion("start_time is null");
return (Criteria) this; return (Criteria) this;

View File

@ -7,7 +7,6 @@
<result column="name" jdbcType="VARCHAR" property="name" /> <result column="name" jdbcType="VARCHAR" property="name" />
<result column="create_user" jdbcType="VARCHAR" property="createUser" /> <result column="create_user" jdbcType="VARCHAR" property="createUser" />
<result column="create_time" jdbcType="BIGINT" property="createTime" /> <result column="create_time" jdbcType="BIGINT" property="createTime" />
<result column="execute_time" jdbcType="BIGINT" property="executeTime" />
<result column="start_time" jdbcType="BIGINT" property="startTime" /> <result column="start_time" jdbcType="BIGINT" property="startTime" />
<result column="end_time" jdbcType="BIGINT" property="endTime" /> <result column="end_time" jdbcType="BIGINT" property="endTime" />
<result column="exec_status" jdbcType="VARCHAR" property="execStatus" /> <result column="exec_status" jdbcType="VARCHAR" property="execStatus" />
@ -80,9 +79,9 @@
</where> </where>
</sql> </sql>
<sql id="Base_Column_List"> <sql id="Base_Column_List">
id, test_plan_id, `name`, create_user, create_time, execute_time, start_time, end_time, id, test_plan_id, `name`, create_user, create_time, start_time, end_time, exec_status,
exec_status, result_status, pass_rate, trigger_mode, pass_threshold, project_id, result_status, pass_rate, trigger_mode, pass_threshold, project_id, integrated, deleted,
integrated, deleted, execute_rate, parent_id execute_rate, parent_id
</sql> </sql>
<select id="selectByExample" parameterType="io.metersphere.plan.domain.TestPlanReportExample" resultMap="BaseResultMap"> <select id="selectByExample" parameterType="io.metersphere.plan.domain.TestPlanReportExample" resultMap="BaseResultMap">
select select
@ -116,19 +115,17 @@
</delete> </delete>
<insert id="insert" parameterType="io.metersphere.plan.domain.TestPlanReport"> <insert id="insert" parameterType="io.metersphere.plan.domain.TestPlanReport">
insert into test_plan_report (id, test_plan_id, `name`, insert into test_plan_report (id, test_plan_id, `name`,
create_user, create_time, execute_time, create_user, create_time, start_time,
start_time, end_time, exec_status, end_time, exec_status, result_status,
result_status, pass_rate, trigger_mode, pass_rate, trigger_mode, pass_threshold,
pass_threshold, project_id, integrated, project_id, integrated, deleted,
deleted, execute_rate, parent_id execute_rate, parent_id)
)
values (#{id,jdbcType=VARCHAR}, #{testPlanId,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR}, values (#{id,jdbcType=VARCHAR}, #{testPlanId,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR},
#{createUser,jdbcType=VARCHAR}, #{createTime,jdbcType=BIGINT}, #{executeTime,jdbcType=BIGINT}, #{createUser,jdbcType=VARCHAR}, #{createTime,jdbcType=BIGINT}, #{startTime,jdbcType=BIGINT},
#{startTime,jdbcType=BIGINT}, #{endTime,jdbcType=BIGINT}, #{execStatus,jdbcType=VARCHAR}, #{endTime,jdbcType=BIGINT}, #{execStatus,jdbcType=VARCHAR}, #{resultStatus,jdbcType=VARCHAR},
#{resultStatus,jdbcType=VARCHAR}, #{passRate,jdbcType=DECIMAL}, #{triggerMode,jdbcType=VARCHAR}, #{passRate,jdbcType=DECIMAL}, #{triggerMode,jdbcType=VARCHAR}, #{passThreshold,jdbcType=DECIMAL},
#{passThreshold,jdbcType=DECIMAL}, #{projectId,jdbcType=VARCHAR}, #{integrated,jdbcType=BIT}, #{projectId,jdbcType=VARCHAR}, #{integrated,jdbcType=BIT}, #{deleted,jdbcType=BIT},
#{deleted,jdbcType=BIT}, #{executeRate,jdbcType=DECIMAL}, #{parentId,jdbcType=VARCHAR} #{executeRate,jdbcType=DECIMAL}, #{parentId,jdbcType=VARCHAR})
)
</insert> </insert>
<insert id="insertSelective" parameterType="io.metersphere.plan.domain.TestPlanReport"> <insert id="insertSelective" parameterType="io.metersphere.plan.domain.TestPlanReport">
insert into test_plan_report insert into test_plan_report
@ -148,9 +145,6 @@
<if test="createTime != null"> <if test="createTime != null">
create_time, create_time,
</if> </if>
<if test="executeTime != null">
execute_time,
</if>
<if test="startTime != null"> <if test="startTime != null">
start_time, start_time,
</if> </if>
@ -204,9 +198,6 @@
<if test="createTime != null"> <if test="createTime != null">
#{createTime,jdbcType=BIGINT}, #{createTime,jdbcType=BIGINT},
</if> </if>
<if test="executeTime != null">
#{executeTime,jdbcType=BIGINT},
</if>
<if test="startTime != null"> <if test="startTime != null">
#{startTime,jdbcType=BIGINT}, #{startTime,jdbcType=BIGINT},
</if> </if>
@ -269,9 +260,6 @@
<if test="record.createTime != null"> <if test="record.createTime != null">
create_time = #{record.createTime,jdbcType=BIGINT}, create_time = #{record.createTime,jdbcType=BIGINT},
</if> </if>
<if test="record.executeTime != null">
execute_time = #{record.executeTime,jdbcType=BIGINT},
</if>
<if test="record.startTime != null"> <if test="record.startTime != null">
start_time = #{record.startTime,jdbcType=BIGINT}, start_time = #{record.startTime,jdbcType=BIGINT},
</if> </if>
@ -320,7 +308,6 @@
`name` = #{record.name,jdbcType=VARCHAR}, `name` = #{record.name,jdbcType=VARCHAR},
create_user = #{record.createUser,jdbcType=VARCHAR}, create_user = #{record.createUser,jdbcType=VARCHAR},
create_time = #{record.createTime,jdbcType=BIGINT}, create_time = #{record.createTime,jdbcType=BIGINT},
execute_time = #{record.executeTime,jdbcType=BIGINT},
start_time = #{record.startTime,jdbcType=BIGINT}, start_time = #{record.startTime,jdbcType=BIGINT},
end_time = #{record.endTime,jdbcType=BIGINT}, end_time = #{record.endTime,jdbcType=BIGINT},
exec_status = #{record.execStatus,jdbcType=VARCHAR}, exec_status = #{record.execStatus,jdbcType=VARCHAR},
@ -352,9 +339,6 @@
<if test="createTime != null"> <if test="createTime != null">
create_time = #{createTime,jdbcType=BIGINT}, create_time = #{createTime,jdbcType=BIGINT},
</if> </if>
<if test="executeTime != null">
execute_time = #{executeTime,jdbcType=BIGINT},
</if>
<if test="startTime != null"> <if test="startTime != null">
start_time = #{startTime,jdbcType=BIGINT}, start_time = #{startTime,jdbcType=BIGINT},
</if> </if>
@ -400,7 +384,6 @@
`name` = #{name,jdbcType=VARCHAR}, `name` = #{name,jdbcType=VARCHAR},
create_user = #{createUser,jdbcType=VARCHAR}, create_user = #{createUser,jdbcType=VARCHAR},
create_time = #{createTime,jdbcType=BIGINT}, create_time = #{createTime,jdbcType=BIGINT},
execute_time = #{executeTime,jdbcType=BIGINT},
start_time = #{startTime,jdbcType=BIGINT}, start_time = #{startTime,jdbcType=BIGINT},
end_time = #{endTime,jdbcType=BIGINT}, end_time = #{endTime,jdbcType=BIGINT},
exec_status = #{execStatus,jdbcType=VARCHAR}, exec_status = #{execStatus,jdbcType=VARCHAR},
@ -417,18 +400,17 @@
</update> </update>
<insert id="batchInsert" parameterType="map"> <insert id="batchInsert" parameterType="map">
insert into test_plan_report insert into test_plan_report
(id, test_plan_id, `name`, create_user, create_time, execute_time, start_time, end_time, (id, test_plan_id, `name`, create_user, create_time, start_time, end_time, exec_status,
exec_status, result_status, pass_rate, trigger_mode, pass_threshold, project_id, result_status, pass_rate, trigger_mode, pass_threshold, project_id, integrated,
integrated, deleted, execute_rate, parent_id) deleted, execute_rate, parent_id)
values values
<foreach collection="list" item="item" separator=","> <foreach collection="list" item="item" separator=",">
(#{item.id,jdbcType=VARCHAR}, #{item.testPlanId,jdbcType=VARCHAR}, #{item.name,jdbcType=VARCHAR}, (#{item.id,jdbcType=VARCHAR}, #{item.testPlanId,jdbcType=VARCHAR}, #{item.name,jdbcType=VARCHAR},
#{item.createUser,jdbcType=VARCHAR}, #{item.createTime,jdbcType=BIGINT}, #{item.executeTime,jdbcType=BIGINT}, #{item.createUser,jdbcType=VARCHAR}, #{item.createTime,jdbcType=BIGINT}, #{item.startTime,jdbcType=BIGINT},
#{item.startTime,jdbcType=BIGINT}, #{item.endTime,jdbcType=BIGINT}, #{item.execStatus,jdbcType=VARCHAR}, #{item.endTime,jdbcType=BIGINT}, #{item.execStatus,jdbcType=VARCHAR}, #{item.resultStatus,jdbcType=VARCHAR},
#{item.resultStatus,jdbcType=VARCHAR}, #{item.passRate,jdbcType=DECIMAL}, #{item.triggerMode,jdbcType=VARCHAR}, #{item.passRate,jdbcType=DECIMAL}, #{item.triggerMode,jdbcType=VARCHAR}, #{item.passThreshold,jdbcType=DECIMAL},
#{item.passThreshold,jdbcType=DECIMAL}, #{item.projectId,jdbcType=VARCHAR}, #{item.integrated,jdbcType=BIT}, #{item.projectId,jdbcType=VARCHAR}, #{item.integrated,jdbcType=BIT}, #{item.deleted,jdbcType=BIT},
#{item.deleted,jdbcType=BIT}, #{item.executeRate,jdbcType=DECIMAL}, #{item.parentId,jdbcType=VARCHAR} #{item.executeRate,jdbcType=DECIMAL}, #{item.parentId,jdbcType=VARCHAR})
)
</foreach> </foreach>
</insert> </insert>
<insert id="batchInsertSelective" parameterType="map"> <insert id="batchInsertSelective" parameterType="map">
@ -456,9 +438,6 @@
<if test="'create_time'.toString() == column.value"> <if test="'create_time'.toString() == column.value">
#{item.createTime,jdbcType=BIGINT} #{item.createTime,jdbcType=BIGINT}
</if> </if>
<if test="'execute_time'.toString() == column.value">
#{item.executeTime,jdbcType=BIGINT}
</if>
<if test="'start_time'.toString() == column.value"> <if test="'start_time'.toString() == column.value">
#{item.startTime,jdbcType=BIGINT} #{item.startTime,jdbcType=BIGINT}
</if> </if>

View File

@ -222,6 +222,7 @@ CREATE INDEX idx_pos ON test_plan_report_api_scenario(pos);
-- 测试计划报告 -- 测试计划报告
ALTER TABLE test_plan_report ADD `execute_rate` DECIMAL(10, 4) COMMENT '执行率'; ALTER TABLE test_plan_report ADD `execute_rate` DECIMAL(10, 4) COMMENT '执行率';
ALTER TABLE test_plan_report ADD `parent_id` VARCHAR(50) COMMENT '独立报告的父级ID'; ALTER TABLE test_plan_report ADD `parent_id` VARCHAR(50) COMMENT '独立报告的父级ID';
ALTER TABLE test_plan_report DROP `execute_time`;
-- 计划报告功能用例明细表 -- 计划报告功能用例明细表
ALTER TABLE test_plan_report_function_case ADD `test_plan_collection_id` VARCHAR(50) NOT NULL COMMENT '测试集ID'; ALTER TABLE test_plan_report_function_case ADD `test_plan_collection_id` VARCHAR(50) NOT NULL COMMENT '测试集ID';

View File

@ -6,12 +6,6 @@ import lombok.Data;
@Data @Data
public class TestPlanReportPostParam { public class TestPlanReportPostParam {
@Schema(description = "项目ID")
private String projectId;
@Schema(description = "计划ID")
private String testPlanId;
@Schema(description = "报告ID") @Schema(description = "报告ID")
private String reportId; private String reportId;

View File

@ -13,10 +13,10 @@ public class TestPlanReportDetailResponse {
private String id; private String id;
@Schema(description = "报告名称") @Schema(description = "报告名称")
private String name; private String name;
@Schema(description = "报告开始时间") @Schema(description = "报告创建时间")
private Long createTime;
@Schema(description = "报告开始(执行)时间")
private Long startTime; private Long startTime;
@Schema(description = "报告执行开始时间")
private Long executeTime;
@Schema(description = "报告结束(执行)时间") @Schema(description = "报告结束(执行)时间")
private Long endTime; private Long endTime;
@Schema(description = "报告内容") @Schema(description = "报告内容")

View File

@ -59,4 +59,6 @@ public interface ExtTestPlanReportMapper {
List<ReportDTO> getCaseReports(@Param("ids") List<String> ids); List<ReportDTO> getCaseReports(@Param("ids") List<String> ids);
List<ReportDTO> getScenarioReports(@Param("ids") List<String> ids); List<ReportDTO> getScenarioReports(@Param("ids") List<String> ids);
void batchUpdateExecuteTime(@Param("startTime") long startTime,@Param("ids")List<String> ids);
} }

View File

@ -217,6 +217,15 @@
</foreach> </foreach>
</select> </select>
<update id="batchUpdateExecuteTime" parameterType="java.lang.String">
update test_plan_report
set start_time = #{startTime}
where id in
<foreach collection="ids" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</update>
<sql id="queryWhereConditionByParentId"> <sql id="queryWhereConditionByParentId">
<where> <where>
<if test="request.reportId != null and request.reportId != ''"> <if test="request.reportId != null and request.reportId != ''">

View File

@ -1,18 +1,23 @@
package io.metersphere.plan.service; package io.metersphere.plan.service;
import com.esotericsoftware.minlog.Log; import com.esotericsoftware.minlog.Log;
import io.metersphere.plan.dto.TestPlanReportPostParam;
import io.metersphere.plan.domain.*; import io.metersphere.plan.domain.*;
import io.metersphere.plan.dto.request.TestPlanBatchExecuteRequest; import io.metersphere.plan.dto.request.TestPlanBatchExecuteRequest;
import io.metersphere.plan.dto.request.TestPlanExecuteRequest; import io.metersphere.plan.dto.request.TestPlanExecuteRequest;
import io.metersphere.plan.dto.request.TestPlanReportGenRequest;
import io.metersphere.plan.mapper.ExtTestPlanReportMapper;
import io.metersphere.plan.mapper.TestPlanCollectionMapper; import io.metersphere.plan.mapper.TestPlanCollectionMapper;
import io.metersphere.plan.mapper.TestPlanConfigMapper; import io.metersphere.plan.mapper.TestPlanConfigMapper;
import io.metersphere.plan.mapper.TestPlanMapper; import io.metersphere.plan.mapper.TestPlanMapper;
import io.metersphere.sdk.constants.ApiBatchRunMode; import io.metersphere.sdk.constants.ApiBatchRunMode;
import io.metersphere.sdk.constants.CaseType; import io.metersphere.sdk.constants.CaseType;
import io.metersphere.sdk.constants.ExecStatus;
import io.metersphere.sdk.constants.TestPlanConstants; import io.metersphere.sdk.constants.TestPlanConstants;
import io.metersphere.sdk.dto.queue.TestPlanExecutionQueue; import io.metersphere.sdk.dto.queue.TestPlanExecutionQueue;
import io.metersphere.sdk.exception.MSException; import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.util.JSON; import io.metersphere.sdk.util.JSON;
import io.metersphere.sdk.util.LogUtils;
import io.metersphere.system.uid.IDGenerator; import io.metersphere.system.uid.IDGenerator;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.CollectionUtils;
@ -24,6 +29,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@Service @Service
@ -33,10 +39,14 @@ public class TestPlanExecuteService {
@Resource @Resource
private TestPlanMapper testPlanMapper; private TestPlanMapper testPlanMapper;
@Resource @Resource
private ExtTestPlanReportMapper extTestPlanReportMapper;
@Resource
private TestPlanConfigMapper testPlanConfigMapper; private TestPlanConfigMapper testPlanConfigMapper;
@Resource @Resource
private TestPlanService testPlanService; private TestPlanService testPlanService;
@Resource @Resource
private TestPlanReportService testPlanReportService;
@Resource
private TestPlanCollectionMapper testPlanCollectionMapper; private TestPlanCollectionMapper testPlanCollectionMapper;
@Resource @Resource
private TestPlanApiCasePlanRunService testPlanApiCasePlanRunService; private TestPlanApiCasePlanRunService testPlanApiCasePlanRunService;
@ -121,12 +131,20 @@ public class TestPlanExecuteService {
if (testPlan == null || StringUtils.equalsIgnoreCase(testPlan.getStatus(), TestPlanConstants.TEST_PLAN_STATUS_ARCHIVED)) { if (testPlan == null || StringUtils.equalsIgnoreCase(testPlan.getStatus(), TestPlanConstants.TEST_PLAN_STATUS_ARCHIVED)) {
throw new MSException("test_plan.error"); throw new MSException("test_plan.error");
} }
TestPlanReportGenRequest genReportRequest = new TestPlanReportGenRequest();
genReportRequest.setTriggerMode(executionQueue.getExecutionSource());
genReportRequest.setTestPlanId(executionQueue.getSourceID());
genReportRequest.setProjectId(testPlan.getProjectId());
if (StringUtils.equalsIgnoreCase(testPlan.getType(), TestPlanConstants.TEST_PLAN_TYPE_GROUP)) { if (StringUtils.equalsIgnoreCase(testPlan.getType(), TestPlanConstants.TEST_PLAN_TYPE_GROUP)) {
List<TestPlan> children = testPlanService.selectNotArchivedChildren(testPlan.getId()); List<TestPlan> children = testPlanService.selectNotArchivedChildren(testPlan.getId());
// 预生成计划组报告
Map<String, String> reportMap = testPlanReportService.genReportByExecution(executionQueue.getPrepareReportId(),genReportRequest, executionQueue.getCreateUser());
long pos = 0; long pos = 0;
List<TestPlanExecutionQueue> childrenQueue = new ArrayList<>(); List<TestPlanExecutionQueue> childrenQueue = new ArrayList<>();
String queueId = IDGenerator.nextStr();
String queueType = QUEUE_PREFIX_TEST_PLAN_GROUP_EXECUTE; String queueType = QUEUE_PREFIX_TEST_PLAN_GROUP_EXECUTE;
String queueId = executionQueue.getPrepareReportId();
for (TestPlan child : children) { for (TestPlan child : children) {
childrenQueue.add( childrenQueue.add(
new TestPlanExecutionQueue( new TestPlanExecutionQueue(
@ -140,7 +158,7 @@ public class TestPlanExecuteService {
child.getId(), child.getId(),
executionQueue.getRunMode(), executionQueue.getRunMode(),
executionQueue.getExecutionSource(), executionQueue.getExecutionSource(),
IDGenerator.nextStr() reportMap.get(child.getId())
) )
); );
} }
@ -150,22 +168,26 @@ public class TestPlanExecuteService {
} else { } else {
this.setRedisForList(genQueueKey(queueId, queueType), childrenQueue.stream().map(JSON::toJSONString).toList()); this.setRedisForList(genQueueKey(queueId, queueType), childrenQueue.stream().map(JSON::toJSONString).toList());
// todo Song-cc 这里是否要生成测试计划组的集合报告并且记录测试计划里用例的执行信息 // 更新报告的执行时间
extTestPlanReportMapper.batchUpdateExecuteTime(System.currentTimeMillis(),reportMap.values().stream().toList());
if (StringUtils.equalsIgnoreCase(executionQueue.getRunMode(), ApiBatchRunMode.SERIAL.name())) { if (StringUtils.equalsIgnoreCase(executionQueue.getRunMode(), ApiBatchRunMode.SERIAL.name())) {
//串行 //串行
TestPlanExecutionQueue nextQueue = this.getNextQueue(queueId, queueType); TestPlanExecutionQueue nextQueue = this.getNextQueue(queueId, queueType);
executeTestPlanOrGroup(nextQueue); executeTestPlan(nextQueue);
} else { } else {
//并行 //并行
childrenQueue.forEach(childQueue -> { childrenQueue.forEach(childQueue -> {
executeTestPlanOrGroup(childQueue); executeTestPlan(childQueue);
}); });
} }
} }
return executionQueue.getPrepareReportId(); return executionQueue.getPrepareReportId();
} else { } else {
Map<String, String> reportMap = testPlanReportService.genReportByExecution(executionQueue.getPrepareReportId(),genReportRequest, executionQueue.getCreateUser());
executionQueue.setPrepareReportId(reportMap.get(executionQueue.getSourceID()));
extTestPlanReportMapper.batchUpdateExecuteTime(System.currentTimeMillis(),reportMap.values().stream().toList());
return this.executeTestPlan(executionQueue); return this.executeTestPlan(executionQueue);
} }
} }
@ -185,7 +207,7 @@ public class TestPlanExecuteService {
TestPlanConfig testPlanConfig = testPlanConfigMapper.selectByPrimaryKey(testPlan.getId()); TestPlanConfig testPlanConfig = testPlanConfigMapper.selectByPrimaryKey(testPlan.getId());
String runMode = StringUtils.isBlank(testPlanConfig.getCaseRunMode()) ? ApiBatchRunMode.SERIAL.name() : testPlanConfig.getCaseRunMode(); String runMode = StringUtils.isBlank(testPlanConfig.getCaseRunMode()) ? ApiBatchRunMode.SERIAL.name() : testPlanConfig.getCaseRunMode();
String queueId = IDGenerator.nextStr(); String queueId = executionQueue.getPrepareReportId();
String queueType = QUEUE_PREFIX_TEST_PLAN_CASE_TYPE; String queueType = QUEUE_PREFIX_TEST_PLAN_CASE_TYPE;
List<TestPlanExecutionQueue> childrenQueue = new ArrayList<>(); List<TestPlanExecutionQueue> childrenQueue = new ArrayList<>();
for (TestPlanCollection collection : testPlanCollectionList) { for (TestPlanCollection collection : testPlanCollectionList) {
@ -201,7 +223,7 @@ public class TestPlanExecuteService {
collection.getId(), collection.getId(),
runMode, runMode,
executionQueue.getExecutionSource(), executionQueue.getExecutionSource(),
IDGenerator.nextStr()) executionQueue.getPrepareReportId())
); );
} }
@ -210,7 +232,6 @@ public class TestPlanExecuteService {
this.testPlanExecuteQueueFinish(executionQueue.getQueueId(), executionQueue.getQueueType()); this.testPlanExecuteQueueFinish(executionQueue.getQueueId(), executionQueue.getQueueType());
} else { } else {
this.setRedisForList(genQueueKey(queueId, queueType), childrenQueue.stream().map(JSON::toJSONString).toList()); this.setRedisForList(genQueueKey(queueId, queueType), childrenQueue.stream().map(JSON::toJSONString).toList());
// todo Song-cc 这里是否要生成测试计划报告并且记录测试计划里用例的执行信息
//开始根据测试计划集合执行测试用例 //开始根据测试计划集合执行测试用例
if (StringUtils.equalsIgnoreCase(runMode, ApiBatchRunMode.SERIAL.name())) { if (StringUtils.equalsIgnoreCase(runMode, ApiBatchRunMode.SERIAL.name())) {
@ -253,7 +274,7 @@ public class TestPlanExecuteService {
collection.getId(), collection.getId(),
collection.getExecuteMethod(), collection.getExecuteMethod(),
executionQueue.getExecutionSource(), executionQueue.getExecutionSource(),
IDGenerator.nextStr()) {{ executionQueue.getPrepareReportId()) {{
this.setTestPlanCollectionJson(JSON.toJSONString(collection)); this.setTestPlanCollectionJson(JSON.toJSONString(collection));
}} }}
); );
@ -404,12 +425,43 @@ public class TestPlanExecuteService {
} }
} }
private void summaryTestPlanReport(String reportId,boolean isGroupReport){
try {
if(isGroupReport){
testPlanReportService.summaryGroupReport(reportId);
}else {
testPlanReportService.summaryPlanReport(reportId);
}
TestPlanReportPostParam postParam = new TestPlanReportPostParam();
postParam.setReportId(reportId);
// 执行生成报告, 执行状态为已完成, 执行及结束时间为当前时间
postParam.setEndTime(System.currentTimeMillis());
postParam.setExecStatus(ExecStatus.COMPLETED.name());
testPlanReportService.postHandleReport(postParam);
}catch (Exception e){
LogUtils.error("Cannot find test plan report for " + reportId, e);
}
}
private void queueExecuteFinish(TestPlanExecutionQueue queue) { private void queueExecuteFinish(TestPlanExecutionQueue queue) {
if (StringUtils.equalsIgnoreCase(queue.getParentQueueType(), QUEUE_PREFIX_TEST_PLAN_BATCH_EXECUTE)) { if (StringUtils.equalsIgnoreCase(queue.getParentQueueType(), QUEUE_PREFIX_TEST_PLAN_BATCH_EXECUTE)) {
// todo Song-cc 测试计划组集合报告生成 if(StringUtils.equalsIgnoreCase(queue.getQueueType(),QUEUE_PREFIX_TEST_PLAN_GROUP_EXECUTE)){
// 计划组报告汇总并统计
this.summaryTestPlanReport(queue.getQueueId(),true);
}else if(StringUtils.equalsIgnoreCase(queue.getQueueType(),QUEUE_PREFIX_TEST_PLAN_CASE_TYPE)){
/*
此时处于批量勾选执行中的游离态测试计划执行所以队列顺序为QUEUE_PREFIX_TEST_PLAN_BATCH_EXECUTE -> QUEUE_PREFIX_TEST_PLAN_CASE_TYPE
此时queue节点为testPlanCollection的节点 而测试计划节点串行状态下在执行之前就被弹出了
所以获取报告ID的方式为读取queueId caseType队列和collection队列的queueId都是报告ID
*/
this.summaryTestPlanReport(queue.getQueueId(),false);
}
this.testPlanGroupQueueFinish(queue.getParentQueueId(), queue.getParentQueueType()); this.testPlanGroupQueueFinish(queue.getParentQueueId(), queue.getParentQueueType());
} else if (StringUtils.equalsIgnoreCase(queue.getParentQueueType(), QUEUE_PREFIX_TEST_PLAN_GROUP_EXECUTE)) { } else if (StringUtils.equalsIgnoreCase(queue.getParentQueueType(), QUEUE_PREFIX_TEST_PLAN_GROUP_EXECUTE)) {
// todo Song-cc 测试计划报告计算 // 计划报告汇总并统计
this.summaryTestPlanReport(queue.getQueueId(),false);
this.testPlanExecuteQueueFinish(queue.getParentQueueId(), queue.getParentQueueType()); this.testPlanExecuteQueueFinish(queue.getParentQueueId(), queue.getParentQueueType());
} else if (StringUtils.equalsIgnoreCase(queue.getParentQueueType(), QUEUE_PREFIX_TEST_PLAN_CASE_TYPE)) { } else if (StringUtils.equalsIgnoreCase(queue.getParentQueueType(), QUEUE_PREFIX_TEST_PLAN_CASE_TYPE)) {
this.caseTypeExecuteQueueFinish(queue.getParentQueueId(), queue.getParentQueueType()); this.caseTypeExecuteQueueFinish(queue.getParentQueueId(), queue.getParentQueueType());
@ -428,17 +480,12 @@ public class TestPlanExecuteService {
ListOperations<String, String> listOps = redisTemplate.opsForList(); ListOperations<String, String> listOps = redisTemplate.opsForList();
String queueDetail = listOps.leftPop(queueKey); String queueDetail = listOps.leftPop(queueKey);
if (StringUtils.isBlank(queueDetail)) { if (StringUtils.isBlank(queueDetail)) {
// 重试2次获取 // 重试1次获取
for (int i = 0; i < 3; i++) {
queueDetail = redisTemplate.opsForList().leftPop(queueKey);
if (StringUtils.isNotBlank(queueDetail)) {
break;
}
try { try {
Thread.sleep(1000); Thread.sleep(1000);
} catch (Exception ignore) { } catch (Exception ignore) {
} }
} queueDetail = redisTemplate.opsForList().leftPop(queueKey);
} }
if (StringUtils.isNotBlank(queueDetail)) { if (StringUtils.isNotBlank(queueDetail)) {

View File

@ -10,6 +10,7 @@ import io.metersphere.plan.dto.response.TestPlanReportDetailResponse;
import io.metersphere.plan.dto.response.TestPlanReportPageResponse; import io.metersphere.plan.dto.response.TestPlanReportPageResponse;
import io.metersphere.plan.enums.TestPlanReportAttachmentSourceType; import io.metersphere.plan.enums.TestPlanReportAttachmentSourceType;
import io.metersphere.plan.mapper.*; import io.metersphere.plan.mapper.*;
import io.metersphere.plan.utils.CountUtils;
import io.metersphere.plan.utils.ModuleTreeUtils; import io.metersphere.plan.utils.ModuleTreeUtils;
import io.metersphere.plan.utils.RateCalculateUtils; import io.metersphere.plan.utils.RateCalculateUtils;
import io.metersphere.plugin.platform.dto.SelectOption; import io.metersphere.plugin.platform.dto.SelectOption;
@ -43,7 +44,6 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Service @Service
@ -215,8 +215,8 @@ public class TestPlanReportService {
* @param request 请求参数 * @param request 请求参数
* @param currentUser 当前用户 * @param currentUser 当前用户
*/ */
public void genReportByManual(TestPlanReportGenRequest request, String currentUser) { public Map<String, String> genReportByManual(TestPlanReportGenRequest request, String currentUser) {
genReport(request, true, currentUser, "/test-plan/report/gen"); return genReport(IDGenerator.nextStr(),request, true, currentUser, "/test-plan/report/gen");
} }
/** /**
@ -225,11 +225,13 @@ public class TestPlanReportService {
* @param request 请求参数 * @param request 请求参数
* @param currentUser 当前用户 * @param currentUser 当前用户
*/ */
public void genReportByExecution(TestPlanReportGenRequest request, String currentUser) { public Map<String, String> genReportByExecution(String prepareReportId,TestPlanReportGenRequest request, String currentUser) {
genReport(request, false, currentUser, "/test-plan/report/gen"); return genReport(prepareReportId,request, false, currentUser, "/test-plan/report/gen");
} }
public void genReport(TestPlanReportGenRequest request, boolean manual, String currentUser, String logPath) { public Map<String, String> genReport(String prepareReportId,TestPlanReportGenRequest request, boolean manual, String currentUser, String logPath) {
Map<String, String> preReportMap = new HashMap<>();
try {
// 所有计划 // 所有计划
List<TestPlan> plans = getPlans(request.getTestPlanId()); List<TestPlan> plans = getPlans(request.getTestPlanId());
// 模块参数 // 模块参数
@ -242,23 +244,24 @@ public class TestPlanReportService {
* 3. 报告后置处理 (计算通过率, 执行率, 执行状态...) {执行时跳过} * 3. 报告后置处理 (计算通过率, 执行率, 执行状态...) {执行时跳过}
*/ */
List<String> childPlanIds = plans.stream().filter(plan -> StringUtils.equals(plan.getType(), TestPlanConstants.TEST_PLAN_TYPE_PLAN)).map(TestPlan::getId).toList(); List<String> childPlanIds = plans.stream().filter(plan -> StringUtils.equals(plan.getType(), TestPlanConstants.TEST_PLAN_TYPE_PLAN)).map(TestPlan::getId).toList();
AtomicReference<String> groupReportId = new AtomicReference<>();
boolean isGroupReports = plans.size() > 1;
plans.forEach(plan -> { plans.forEach(plan -> {
request.setTestPlanId(plan.getId()); request.setTestPlanId(plan.getId());
TestPlanReportGenPreParam genPreParam = buildReportGenParam(request, plan, groupReportId.get()); TestPlanReportGenPreParam genPreParam = buildReportGenParam(request, plan, prepareReportId);
genPreParam.setUseManual(manual); genPreParam.setUseManual(manual);
TestPlanReport preReport = preGenReport(genPreParam, currentUser, logPath, moduleParam, childPlanIds); //如果是测试计划的独立报告使用参数中的预生成的报告id否则只有测试计划组报告使用该id
if (genPreParam.getIntegrated()) { String prepareItemReportId = isGroupReports?IDGenerator.nextStr() : prepareReportId;
// 如果是计划组的报告, 初始化组的报告ID TestPlanReport preReport = preGenReport(prepareItemReportId,genPreParam, currentUser, logPath, moduleParam, childPlanIds);
groupReportId.set(preReport.getId());
}
if (manual) { if (manual) {
// 汇总 // 汇总
summaryReport(preReport.getId()); if (genPreParam.getIntegrated()) {
summaryGroupReport(preReport.getId());
} else {
summaryPlanReport(preReport.getId());
}
// 手动生成的报告, 汇总结束后直接进行后置处理 // 手动生成的报告, 汇总结束后直接进行后置处理
TestPlanReportPostParam postParam = new TestPlanReportPostParam(); TestPlanReportPostParam postParam = new TestPlanReportPostParam();
BeanUtils.copyBean(postParam, request);
postParam.setReportId(preReport.getId()); postParam.setReportId(preReport.getId());
// 手动生成报告, 执行状态为已完成, 执行及结束时间为当前时间 // 手动生成报告, 执行状态为已完成, 执行及结束时间为当前时间
postParam.setExecuteTime(System.currentTimeMillis()); postParam.setExecuteTime(System.currentTimeMillis());
@ -266,14 +269,19 @@ public class TestPlanReportService {
postParam.setExecStatus(ExecStatus.COMPLETED.name()); postParam.setExecStatus(ExecStatus.COMPLETED.name());
postHandleReport(postParam); postHandleReport(postParam);
} }
preReportMap.put(plan.getId(), preReport.getId());
}); });
} catch (Exception e) {
LogUtils.error("生成报告异常: " + e.getMessage());
}
return preReportMap;
} }
/** /**
* 预生成报告内容(汇总前调用) * 预生成报告内容(汇总前调用)
* @return 报告 * @return 报告
*/ */
public TestPlanReport preGenReport(TestPlanReportGenPreParam genParam, String currentUser, String logPath, TestPlanReportModuleParam moduleParam, List<String> childPlanIds) { public TestPlanReport preGenReport(String prepareId,TestPlanReportGenPreParam genParam, String currentUser, String logPath, TestPlanReportModuleParam moduleParam, List<String> childPlanIds) {
// 计划配置 // 计划配置
TestPlanConfig config = testPlanConfigMapper.selectByPrimaryKey(genParam.getTestPlanId()); TestPlanConfig config = testPlanConfigMapper.selectByPrimaryKey(genParam.getTestPlanId());
@ -284,7 +292,7 @@ public class TestPlanReportService {
*/ */
TestPlanReport report = new TestPlanReport(); TestPlanReport report = new TestPlanReport();
BeanUtils.copyBean(report, genParam); BeanUtils.copyBean(report, genParam);
report.setId(IDGenerator.nextStr()); report.setId(genParam.getIntegrated() ? genParam.getGroupReportId() : prepareId);
report.setName(genParam.getTestPlanName() + "-" + DateUtils.getTimeStr(System.currentTimeMillis())); report.setName(genParam.getTestPlanName() + "-" + DateUtils.getTimeStr(System.currentTimeMillis()));
report.setCreateUser(currentUser); report.setCreateUser(currentUser);
report.setCreateTime(System.currentTimeMillis()); report.setCreateTime(System.currentTimeMillis());
@ -398,7 +406,7 @@ public class TestPlanReportService {
if (genParam.getIntegrated()) { if (genParam.getIntegrated()) {
reportBugs = CollectionUtils.isEmpty(childPlanIds) ? new ArrayList<>() : extTestPlanReportBugMapper.getGroupBugs(childPlanIds); reportBugs = CollectionUtils.isEmpty(childPlanIds) ? new ArrayList<>() : extTestPlanReportBugMapper.getGroupBugs(childPlanIds);
} else { } else {
reportBugs = extTestPlanReportBugMapper.getPlanBugs(genParam.getTestPlanId());; reportBugs = extTestPlanReportBugMapper.getPlanBugs(genParam.getTestPlanId());
} }
if (CollectionUtils.isNotEmpty(reportBugs)) { if (CollectionUtils.isNotEmpty(reportBugs)) {
// MS处理人会与第三方的值冲突, 分开查询 // MS处理人会与第三方的值冲突, 分开查询
@ -531,11 +539,11 @@ public class TestPlanReportService {
} }
/** /**
* 汇总规划生成的报告 * 汇总生成的计划报告
* *
* @param reportId 报告ID * @param reportId 报告ID
*/ */
public void summaryReport(String reportId) { public void summaryPlanReport(String reportId) {
TestPlanReportSummaryExample example = new TestPlanReportSummaryExample(); TestPlanReportSummaryExample example = new TestPlanReportSummaryExample();
example.createCriteria().andTestPlanReportIdEqualTo(reportId); example.createCriteria().andTestPlanReportIdEqualTo(reportId);
List<TestPlanReportSummary> testPlanReportSummaries = testPlanReportSummaryMapper.selectByExample(example); List<TestPlanReportSummary> testPlanReportSummaries = testPlanReportSummaryMapper.selectByExample(example);
@ -569,6 +577,53 @@ public class TestPlanReportService {
testPlanReportSummaryMapper.updateByPrimaryKeySelective(reportSummary); testPlanReportSummaryMapper.updateByPrimaryKeySelective(reportSummary);
} }
/**
* 汇总生成的计划组报告
* @param reportId
*/
public void summaryGroupReport(String reportId) {
TestPlanReportSummaryExample summaryExample = new TestPlanReportSummaryExample();
summaryExample.createCriteria().andTestPlanReportIdEqualTo(reportId);
List<TestPlanReportSummary> testPlanReportSummaries = testPlanReportSummaryMapper.selectByExample(summaryExample);
if (CollectionUtils.isEmpty(testPlanReportSummaries)) {
// 报告详情不存在
return;
}
TestPlanReportSummary groupSummary = testPlanReportSummaries.get(0);
TestPlanReportExample example = new TestPlanReportExample();
example.createCriteria().andParentIdEqualTo(reportId);
List<TestPlanReport> testPlanReports = testPlanReportMapper.selectByExample(example);
if(CollectionUtils.isEmpty(testPlanReports)){
return;
}
List<String> ids = testPlanReports.stream().map(TestPlanReport::getId).toList();
summaryExample.clear();
summaryExample.createCriteria().andIdIn(ids);
List<TestPlanReportSummary> summaryList = testPlanReportSummaryMapper.selectByExampleWithBLOBs(summaryExample);
List<CaseCount> functionalCaseCountList = new ArrayList<>();
List<CaseCount> apiCaseCountList = new ArrayList<>();
List<CaseCount> scenarioCountList = new ArrayList<>();
List<CaseCount> executeCountList = new ArrayList<>();
summaryList.forEach(summary -> {
CaseCount functionalCaseCount = summary.getFunctionalExecuteResult() == null ? CaseCount.builder().build() : JSON.parseObject(new String(summary.getFunctionalExecuteResult()), CaseCount.class);
CaseCount apiCaseCount = summary.getApiExecuteResult() == null ? CaseCount.builder().build() : JSON.parseObject(new String(summary.getApiExecuteResult()), CaseCount.class);
CaseCount scenarioCount = summary.getScenarioExecuteResult() == null ? CaseCount.builder().build() : JSON.parseObject(new String(summary.getScenarioExecuteResult()), CaseCount.class);
CaseCount executeCount = summary.getExecuteResult() == null ? CaseCount.builder().build() : JSON.parseObject(new String(summary.getExecuteResult()), CaseCount.class);
functionalCaseCountList.add(functionalCaseCount);
apiCaseCountList.add(apiCaseCount);
scenarioCountList.add(scenarioCount);
executeCountList.add(executeCount);
});
// 入库组汇总数据 => 报告详情表
groupSummary.setFunctionalExecuteResult(JSON.toJSONBytes(CountUtils.summarizeProperties(functionalCaseCountList)));
groupSummary.setApiExecuteResult(JSON.toJSONBytes(CountUtils.summarizeProperties(apiCaseCountList)));
groupSummary.setScenarioExecuteResult(JSON.toJSONBytes(CountUtils.summarizeProperties(scenarioCountList)));
groupSummary.setExecuteResult(JSON.toJSONBytes(CountUtils.summarizeProperties(executeCountList)));
testPlanReportSummaryMapper.updateByPrimaryKeySelective(groupSummary);
}
/** /**
* 通过请求参数获取批量操作的ID集合 * 通过请求参数获取批量操作的ID集合
* *
@ -778,8 +833,8 @@ public class TestPlanReportService {
List<TestPlan> testPlans = testPlanMapper.selectByExample(example); List<TestPlan> testPlans = testPlanMapper.selectByExample(example);
plans.addAll(testPlans); plans.addAll(testPlans);
} }
// 保证一条为计划组 // 保证最后一条为计划组
plans.addFirst(testPlan); plans.addLast(testPlan);
return plans; return plans;
} }

View File

@ -0,0 +1,46 @@
package io.metersphere.plan.utils;
import io.metersphere.plan.dto.CaseCount;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.util.LogUtils;
import lombok.experimental.UtilityClass;
import java.lang.reflect.Field;
import java.util.List;
/**
* 汇总工具类
*/
@UtilityClass
public class CountUtils {
/**
* 集合属性汇总
* @param list 集合
* @return 汇总实体
*/
public static CaseCount summarizeProperties(List<CaseCount> list) {
if (list.isEmpty()) {
return new CaseCount();
}
Class<CaseCount> clazz = (Class<CaseCount>) list.get(0).getClass();
CaseCount summary;
try {
summary = clazz.newInstance();
Field[] fields = clazz.getDeclaredFields();
for (CaseCount caseCount : list) {
for (Field field : fields) {
field.setAccessible(true);
Integer value = (Integer) field.get(summary) + (Integer) field.get(caseCount);
field.set(summary, value);
}
}
} catch (Exception e) {
LogUtils.error("汇总数据异常: " + e.getMessage());
throw new MSException(e);
}
return summary;
}
}

View File

@ -267,7 +267,6 @@ public class TestPlanExecuteTests extends BaseTest {
//本条测试用例中最多传入的是5个批量执行4个测试计划组和1个测试计划 有一个测试计划组下面没有测试计划 //本条测试用例中最多传入的是5个批量执行4个测试计划组和1个测试计划 有一个测试计划组下面没有测试计划
// 串行的话模拟其中8个测试集的回调 既while中最多循环8次进行回调每次回调只传入1个测试集防止并行/串行的干扰 // 串行的话模拟其中8个测试集的回调 既while中最多循环8次进行回调每次回调只传入1个测试集防止并行/串行的干扰
int foreachIndex = 8; int foreachIndex = 8;
long timeStem = System.currentTimeMillis();
while (foreachIndex > 0 && !allQueueIds.isEmpty()) { while (foreachIndex > 0 && !allQueueIds.isEmpty()) {
String collectionFinishQueueIds = collectionQueueIdList.getFirst(); String collectionFinishQueueIds = collectionQueueIdList.getFirst();