From 01c7a365e0e449964b87a64de63d061421d0cbe9 Mon Sep 17 00:00:00 2001 From: Jianguo-Genius Date: Wed, 3 Jul 2024 16:09:57 +0800 Subject: [PATCH] =?UTF-8?q?refactor(=E6=B5=8B=E8=AF=95=E8=AE=A1=E5=88=92):?= =?UTF-8?q?=20=E6=B5=8B=E8=AF=95=E8=AE=A1=E5=88=92=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migration/3.1.0/dml/V3.1.0_2_1__data.sql | 5 + .../sdk/constants/TestPlanConstants.java | 14 +- .../controller/ProjectLogController.java | 6 +- .../plan/dto/TestPlanGroupCountDTO.java | 9 + .../dto/TestPlanResourceExecResultDTO.java | 10 + .../dto/request/TestPlanTableRequest.java | 3 + .../plan/dto/response/TestPlanResponse.java | 2 - .../response/TestPlanStatisticsResponse.java | 64 ++++- .../plan/mapper/ExtTestPlanApiCaseMapper.java | 3 +- .../plan/mapper/ExtTestPlanApiCaseMapper.xml | 19 ++ .../mapper/ExtTestPlanApiScenarioMapper.java | 3 + .../mapper/ExtTestPlanApiScenarioMapper.xml | 19 ++ .../ExtTestPlanFunctionalCaseMapper.java | 2 + .../ExtTestPlanFunctionalCaseMapper.xml | 19 ++ .../plan/mapper/ExtTestPlanMapper.java | 5 + .../plan/mapper/ExtTestPlanMapper.xml | 28 ++ .../plan/service/TestPlanApiCaseService.java | 4 + .../service/TestPlanApiScenarioService.java | 4 + .../service/TestPlanBaseUtilsService.java | 67 +++++ .../TestPlanBatchOperationService.java | 4 +- .../plan/service/TestPlanBugService.java | 6 + .../TestPlanFunctionalCaseService.java | 4 + .../service/TestPlanManagementService.java | 85 +++++- .../plan/service/TestPlanResourceService.java | 4 +- .../service/TestPlanSendNoticeService.java | 2 +- .../plan/service/TestPlanService.java | 103 ++++---- .../service/TestPlanStatisticsService.java | 222 ++++++++++------ .../plan/utils/RateCalculateUtils.java | 6 +- .../plan/controller/TestPlanTests.java | 248 +++++++++++++++--- 29 files changed, 770 insertions(+), 200 deletions(-) create mode 100644 backend/services/test-plan/src/main/java/io/metersphere/plan/dto/TestPlanGroupCountDTO.java create mode 100644 backend/services/test-plan/src/main/java/io/metersphere/plan/dto/TestPlanResourceExecResultDTO.java diff --git a/backend/framework/domain/src/main/resources/migration/3.1.0/dml/V3.1.0_2_1__data.sql b/backend/framework/domain/src/main/resources/migration/3.1.0/dml/V3.1.0_2_1__data.sql index 2c2ff176d8..ef813340dd 100644 --- a/backend/framework/domain/src/main/resources/migration/3.1.0/dml/V3.1.0_2_1__data.sql +++ b/backend/framework/domain/src/main/resources/migration/3.1.0/dml/V3.1.0_2_1__data.sql @@ -47,5 +47,10 @@ CALL UpdatePosForNoneGroup(); DROP PROCEDURE IF EXISTS UpdatePosForNoneGroup; -- 清洗测试计划表的pos数据结束 +-- 修改未归档测试计划的存储状态 +UPDATE test_plan +SET status = 'NOT_ARCHIVED' +WHERE status != 'ARCHIVED'; + -- set innodb lock wait timeout to default SET SESSION innodb_lock_wait_timeout = DEFAULT; \ No newline at end of file diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/constants/TestPlanConstants.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/constants/TestPlanConstants.java index 988caf55a6..b3641104a1 100644 --- a/backend/framework/sdk/src/main/java/io/metersphere/sdk/constants/TestPlanConstants.java +++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/constants/TestPlanConstants.java @@ -5,19 +5,21 @@ public class TestPlanConstants { public static final String TEST_PLAN_TYPE_PLAN = "TEST_PLAN"; //测试计划类型-测试计划组 public static final String TEST_PLAN_TYPE_GROUP = "GROUP"; + //测试计划组默认ID public static final String TEST_PLAN_DEFAULT_GROUP_ID = "NONE"; - //测试计划中相关的默认父ID public static final String DEFAULT_PARENT_ID = "NONE"; //测试计划状态-未开始 - public static final String TEST_PLAN_STATUS_PREPARED = "PREPARED"; - //测试计划状态-进行中 - public static final String TEST_PLAN_STATUS_UNDERWAY = "UNDERWAY"; - //测试计划状态-已完成 - public static final String TEST_PLAN_STATUS_COMPLETED = "COMPLETED"; + public static final String TEST_PLAN_STATUS_NOT_ARCHIVED = "NOT_ARCHIVED"; //测试计划状态-已归档 public static final String TEST_PLAN_STATUS_ARCHIVED = "ARCHIVED"; + //测试计划对外展示状态-未开始 + public static final String TEST_PLAN_SHOW_STATUS_PREPARED = "PREPARED"; + //测试计划对外展示状态-进行中 + public static final String TEST_PLAN_SHOW_STATUS_UNDERWAY = "UNDERWAY"; + //测试计划对外展示状态-已完成 + public static final String TEST_PLAN_SHOW_STATUS_COMPLETED = "COMPLETED"; } diff --git a/backend/services/project-management/src/main/java/io/metersphere/project/controller/ProjectLogController.java b/backend/services/project-management/src/main/java/io/metersphere/project/controller/ProjectLogController.java index 086abc412e..4593d426d1 100644 --- a/backend/services/project-management/src/main/java/io/metersphere/project/controller/ProjectLogController.java +++ b/backend/services/project-management/src/main/java/io/metersphere/project/controller/ProjectLogController.java @@ -17,9 +17,9 @@ import io.metersphere.system.utils.SessionUtils; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; import org.apache.commons.lang3.StringUtils; import org.apache.shiro.authz.annotation.RequiresPermissions; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -36,10 +36,10 @@ import java.util.List; @RequestMapping("/project/log") public class ProjectLogController { - @Autowired + @Resource private SimpleUserService simpleUserService; - @Autowired + @Resource private OperationLogService operationLogService; @GetMapping("/user/list/{projectId}") diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/TestPlanGroupCountDTO.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/TestPlanGroupCountDTO.java new file mode 100644 index 0000000000..5e19be9c30 --- /dev/null +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/TestPlanGroupCountDTO.java @@ -0,0 +1,9 @@ +package io.metersphere.plan.dto; + +import lombok.Data; + +@Data +public class TestPlanGroupCountDTO { + private String groupId; + private long count; +} diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/TestPlanResourceExecResultDTO.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/TestPlanResourceExecResultDTO.java new file mode 100644 index 0000000000..2f06f9b097 --- /dev/null +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/TestPlanResourceExecResultDTO.java @@ -0,0 +1,10 @@ +package io.metersphere.plan.dto; + +import lombok.Data; + +@Data +public class TestPlanResourceExecResultDTO { + private String testPlanId; + private String execResult; + private String testPlanGroupId; +} diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/request/TestPlanTableRequest.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/request/TestPlanTableRequest.java index a0e1d35b8e..7bc858a232 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/request/TestPlanTableRequest.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/request/TestPlanTableRequest.java @@ -26,6 +26,9 @@ public class TestPlanTableRequest extends BasePageRequest { @Schema(description = "通过Keyword过滤出的测试子计划的测试计划组id") private List keywordFilterIds; + @Schema(description = "通过其他条件查询出来的,必须要包含的测试计划ID") + private List innerIds; + public String getSortString() { if (StringUtils.isEmpty(super.getSortString())) { return "t.update_time desc"; diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/response/TestPlanResponse.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/response/TestPlanResponse.java index 5d91c5094c..b5fc81253f 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/response/TestPlanResponse.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/response/TestPlanResponse.java @@ -16,8 +16,6 @@ public class TestPlanResponse extends TestPlanStatisticsResponse { private long num; @Schema(description = "名称") private String name; - @Schema(description = "状态") - private String status; @Schema(description = "测试计划类型 测试计划/测试计划组") private String type; @Schema(description = "标签") diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/response/TestPlanStatisticsResponse.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/response/TestPlanStatisticsResponse.java index a3319e2a89..f6e1f04b81 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/response/TestPlanStatisticsResponse.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/response/TestPlanStatisticsResponse.java @@ -2,6 +2,8 @@ package io.metersphere.plan.dto.response; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import io.metersphere.plan.serializer.CustomRateSerializer; +import io.metersphere.plan.utils.RateCalculateUtils; +import io.metersphere.sdk.constants.TestPlanConstants; import io.metersphere.system.dto.request.schedule.BaseScheduleConfigRequest; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @@ -16,7 +18,8 @@ public class TestPlanStatisticsResponse { @Schema(description = "测试计划ID") private String id; - + @Schema(description = "测试计划状态") + private String status; @Schema(description = "测试计划通过阈值{0-100}") @JsonSerialize(using = CustomRateSerializer.class) private Double passThreshold; @@ -33,31 +36,70 @@ public class TestPlanStatisticsResponse { * 执行进度中的用例数量统计 */ @Schema(description = "成功用例数量") - private Integer successCount = 0; + private long successCount = 0; @Schema(description = "失败用例数量") - private Integer errorCount = 0; + private long errorCount = 0; @Schema(description = "误报用例数量") - private Integer fakeErrorCount = 0; + private long fakeErrorCount = 0; @Schema(description = "阻塞用例数量") - private Integer blockCount = 0; + private long blockCount = 0; @Schema(description = "未执行用例数量") - private Integer pendingCount = 0; + private long pendingCount = 0; /** * 用例数中用例数量统计 */ @Schema(description = "用例总数") - private Integer caseTotal = 0; + private long caseTotal = 0; @Schema(description = "功能用例数量") - private Integer functionalCaseCount = 0; + private long functionalCaseCount = 0; @Schema(description = "接口用例数量") - private Integer apiCaseCount = 0; + private long apiCaseCount = 0; @Schema(description = "接口场景数量") - private Integer apiScenarioCount = 0; + private long apiScenarioCount = 0; @Schema(description = "缺陷数量") - private Integer bugCount = 0; + private long bugCount = 0; @Schema(description = "定时任务配置") private BaseScheduleConfigRequest scheduleConfig; @Schema(description = "定时任务下一次执行时间") private Long nextTriggerTime; + + /** + * 计算测试计划状态 + * 未开始:执行进度0% + * 进行中:执行进度不到100% + * 已完成:执行进度100% + */ + public void calculateStatus() { + if (this.successCount == 0 && errorCount == 0 && fakeErrorCount == 0 && blockCount == 0) { + this.status = TestPlanConstants.TEST_PLAN_SHOW_STATUS_PREPARED; + } else if (this.pendingCount == 0) { + this.status = TestPlanConstants.TEST_PLAN_SHOW_STATUS_COMPLETED; + } else { + this.status = TestPlanConstants.TEST_PLAN_SHOW_STATUS_UNDERWAY; + } + } + + public void calculateCaseTotal() { + this.caseTotal = this.functionalCaseCount + this.apiCaseCount + this.apiScenarioCount; + } + + public void calculatePassRate() { + this.passRate = RateCalculateUtils.divWithPrecision(this.successCount, this.caseTotal, 2); + } + + public void calculateExecuteRate() { + this.executeRate = RateCalculateUtils.divWithPrecision(this.successCount - this.pendingCount, this.caseTotal, 2); + } + + public void calculateAllNumber(TestPlanStatisticsResponse childResponse) { + this.functionalCaseCount += childResponse.getFunctionalCaseCount(); + this.apiCaseCount += childResponse.getApiCaseCount(); + this.apiScenarioCount += childResponse.getApiScenarioCount(); + this.successCount += childResponse.getSuccessCount(); + this.errorCount += childResponse.getErrorCount(); + this.fakeErrorCount += childResponse.getFakeErrorCount(); + this.blockCount += childResponse.getBlockCount(); + this.pendingCount += childResponse.getPendingCount(); + } } diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanApiCaseMapper.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanApiCaseMapper.java index e76a2156d6..5479b9820d 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanApiCaseMapper.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanApiCaseMapper.java @@ -1,6 +1,5 @@ package io.metersphere.plan.mapper; -import io.metersphere.api.domain.ApiTestCase; import io.metersphere.api.dto.definition.ApiDefinitionDTO; import io.metersphere.functional.dto.FunctionalCaseModuleCountDTO; import io.metersphere.functional.dto.ProjectOptionDTO; @@ -8,6 +7,7 @@ import io.metersphere.plan.domain.TestPlanApiCase; import io.metersphere.plan.dto.ApiCaseModuleDTO; import io.metersphere.plan.dto.ResourceSelectParam; import io.metersphere.plan.dto.TestPlanCaseRunResultCount; +import io.metersphere.plan.dto.TestPlanResourceExecResultDTO; import io.metersphere.plan.dto.request.TestPlanApiCaseBatchRequest; import io.metersphere.plan.dto.request.TestPlanApiCaseModuleRequest; import io.metersphere.plan.dto.request.TestPlanApiCaseRequest; @@ -75,4 +75,5 @@ public interface ExtTestPlanApiCaseMapper { List getPlanApiCaseNotDeletedByCollectionIds(@Param("collectionIds") List collectionIds); + List selectDistinctExecResult(String projectId); } diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanApiCaseMapper.xml b/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanApiCaseMapper.xml index 9ea6befdcf..9c2836ff07 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanApiCaseMapper.xml +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanApiCaseMapper.xml @@ -717,4 +717,23 @@ WHERE t.test_plan_id = #{request.testPlanId} AND atc.deleted = false + \ No newline at end of file diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanApiScenarioMapper.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanApiScenarioMapper.java index a5c36dbdec..5bf5c101ef 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanApiScenarioMapper.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanApiScenarioMapper.java @@ -6,6 +6,7 @@ import io.metersphere.plan.domain.TestPlanApiScenario; import io.metersphere.plan.dto.ApiScenarioModuleDTO; import io.metersphere.plan.dto.ResourceSelectParam; import io.metersphere.plan.dto.TestPlanCaseRunResultCount; +import io.metersphere.plan.dto.TestPlanResourceExecResultDTO; import io.metersphere.plan.dto.request.BasePlanCaseBatchRequest; import io.metersphere.plan.dto.request.TestPlanApiScenarioBatchRunRequest; import io.metersphere.plan.dto.request.TestPlanApiScenarioModuleRequest; @@ -69,4 +70,6 @@ public interface ExtTestPlanApiScenarioMapper { List getIdsByReportIdAndCollectionId(@Param("testPlanReportId") String testPlanReportId, @Param("collectionId") String collectionId); List getPlanScenarioCaseNotDeletedByCollectionIds(@Param("collectionIds") List collectionIds); + + List selectDistinctExecResult(String projectId); } diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanApiScenarioMapper.xml b/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanApiScenarioMapper.xml index b1dbf3b164..53dcbe7ccd 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanApiScenarioMapper.xml +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanApiScenarioMapper.xml @@ -485,6 +485,25 @@ AND a.deleted = false + diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanFunctionalCaseMapper.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanFunctionalCaseMapper.java index 119c2b3e5a..b33ce00702 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanFunctionalCaseMapper.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanFunctionalCaseMapper.java @@ -6,6 +6,7 @@ import io.metersphere.functional.dto.ProjectOptionDTO; import io.metersphere.plan.domain.TestPlanFunctionalCase; import io.metersphere.plan.dto.ResourceSelectParam; import io.metersphere.plan.dto.TestPlanCaseRunResultCount; +import io.metersphere.plan.dto.TestPlanResourceExecResultDTO; import io.metersphere.plan.dto.request.BasePlanCaseBatchRequest; import io.metersphere.plan.dto.request.TestPlanCaseModuleRequest; import io.metersphere.plan.dto.request.TestPlanCaseRequest; @@ -67,4 +68,5 @@ public interface ExtTestPlanFunctionalCaseMapper { List getPlanCaseNotDeletedByCollectionIds(@Param("collectionIds") List collectionIds); + List selectDistinctExecResult(String projectId); } diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanFunctionalCaseMapper.xml b/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanFunctionalCaseMapper.xml index 4c33a19d25..b39a869739 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanFunctionalCaseMapper.xml +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanFunctionalCaseMapper.xml @@ -578,4 +578,23 @@ GROUP BY test_plan_functional_case.test_plan_collection_id + \ No newline at end of file diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanMapper.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanMapper.java index 7eaaeb98b5..a0744c3b73 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanMapper.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanMapper.java @@ -2,6 +2,7 @@ package io.metersphere.plan.mapper; import io.metersphere.plan.domain.TestPlan; import io.metersphere.plan.dto.TestPlanExecuteHisDTO; +import io.metersphere.plan.dto.TestPlanGroupCountDTO; import io.metersphere.plan.dto.TestPlanQueryConditions; import io.metersphere.plan.dto.request.TestPlanBatchProcessRequest; import io.metersphere.plan.dto.request.TestPlanExecuteHisPageRequest; @@ -66,4 +67,8 @@ public interface ExtTestPlanMapper { List listHis(@Param("request")TestPlanExecuteHisPageRequest request); List selectGroupIdByKeyword(@Param("projectId") String projectId, @Param("keyword") String keyword); + + List countByGroupPlan(String projectId); + + List selectIdByProjectIdAndWithoutList(@Param("projectId") String projectId, @Param("withoutList") List withoutList); } diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanMapper.xml b/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanMapper.xml index f2dec530c2..eddeef7350 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanMapper.xml +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/mapper/ExtTestPlanMapper.xml @@ -106,6 +106,13 @@ ) + + and t.id in + + #{id} + + + AND @@ -483,6 +490,27 @@ or t.tags like concat('%', #{keyword}, '%') ) + + update test_plan diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanApiCaseService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanApiCaseService.java index 5f6e1d2751..1087e90595 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanApiCaseService.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanApiCaseService.java @@ -118,6 +118,10 @@ public class TestPlanApiCaseService extends TestPlanResourceService { @Resource private ExtApiTestCaseMapper extApiTestCaseMapper; + public List selectDistinctExecResult(String projectId) { + return extTestPlanApiCaseMapper.selectDistinctExecResult(projectId); + } + @Override public void deleteBatchByTestPlanId(List testPlanIdList) { TestPlanApiCaseExample example = new TestPlanApiCaseExample(); diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanApiScenarioService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanApiScenarioService.java index 75d08818cd..bffd8bb515 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanApiScenarioService.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanApiScenarioService.java @@ -112,6 +112,10 @@ public class TestPlanApiScenarioService extends TestPlanResourceService { @Resource private TestPlanConfigService testPlanConfigService; + @Override + public List selectDistinctExecResult(String projectId) { + return extTestPlanApiScenarioMapper.selectDistinctExecResult(projectId); + } @Override public void deleteBatchByTestPlanId(List testPlanIdList) { TestPlanApiScenarioExample example = new TestPlanApiScenarioExample(); diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanBaseUtilsService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanBaseUtilsService.java index ffadd62023..10e3ec8751 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanBaseUtilsService.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanBaseUtilsService.java @@ -1,8 +1,10 @@ package io.metersphere.plan.service; +import io.metersphere.plan.dto.TestPlanResourceExecResultDTO; import io.metersphere.plan.mapper.ExtTestPlanMapper; import io.metersphere.plan.mapper.TestPlanMapper; import io.metersphere.sdk.constants.ModuleConstants; +import io.metersphere.sdk.constants.TestPlanConstants; import io.metersphere.sdk.exception.MSException; import io.metersphere.sdk.util.Translator; import io.metersphere.system.domain.TestPlanModuleExample; @@ -12,6 +14,11 @@ import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + @Service @Transactional(rollbackFor = Exception.class) public class TestPlanBaseUtilsService { @@ -40,4 +47,64 @@ public class TestPlanBaseUtilsService { } } + public Map>> parseExecResult(List execResults) { + Map>> returnMap = new HashMap<>(); + for (TestPlanResourceExecResultDTO execResult : execResults) { + String groupId = execResult.getTestPlanGroupId(); + String planId = execResult.getTestPlanId(); + + Map> planMap; + if (returnMap.containsKey(groupId)) { + planMap = returnMap.get(groupId); + List resultList; + if (planMap.containsKey(planId)) { + resultList = planMap.get(planId); + } else { + resultList = new ArrayList<>(); + } + resultList.add(execResult.getExecResult()); + planMap.put(planId, resultList); + } else { + planMap = new HashMap<>(); + List resultList = new ArrayList<>(); + resultList.add(execResult.getExecResult()); + planMap.put(planId, resultList); + } + returnMap.put(groupId, planMap); + } + return returnMap; + } + + public String calculateTestPlanStatus(List resultList) { + //同时包含两种状态:进行中 + if (resultList.size() == 1) { + if (resultList.contains(TestPlanConstants.TEST_PLAN_SHOW_STATUS_COMPLETED)) { + return TestPlanConstants.TEST_PLAN_SHOW_STATUS_COMPLETED; + } else + return TestPlanConstants.TEST_PLAN_SHOW_STATUS_PREPARED; + } else { + return TestPlanConstants.TEST_PLAN_SHOW_STATUS_UNDERWAY; + } + } + + public String calculateStatusByChildren(List childStatus) { + // 首先去重 + childStatus = childStatus.stream().distinct().toList(); + + /* + 1:全部都未开始 则为未开始 + 2:全部都已完成 则为已完成 + 3:包含进行中 则为进行中 + 4:未开始+已完成:进行中 + */ + if (childStatus.contains(TestPlanConstants.TEST_PLAN_SHOW_STATUS_UNDERWAY)) { + return TestPlanConstants.TEST_PLAN_SHOW_STATUS_UNDERWAY; + } else if (childStatus.contains(TestPlanConstants.TEST_PLAN_SHOW_STATUS_COMPLETED) && childStatus.contains(TestPlanConstants.TEST_PLAN_SHOW_STATUS_PREPARED)) { + return TestPlanConstants.TEST_PLAN_SHOW_STATUS_UNDERWAY; + } else if (childStatus.contains(TestPlanConstants.TEST_PLAN_SHOW_STATUS_COMPLETED)) { + return TestPlanConstants.TEST_PLAN_SHOW_STATUS_COMPLETED; + } else { + return TestPlanConstants.TEST_PLAN_SHOW_STATUS_PREPARED; + } + } } diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanBatchOperationService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanBatchOperationService.java index a7085bd754..6b44bee46e 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanBatchOperationService.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanBatchOperationService.java @@ -196,7 +196,7 @@ public class TestPlanBatchOperationService extends TestPlanBaseUtilsService { testPlan.setPos(testPlanGroupService.getNextOrder(targetId)); testPlan.setActualEndTime(null); testPlan.setActualStartTime(null); - testPlan.setStatus(TestPlanConstants.TEST_PLAN_STATUS_PREPARED); + testPlan.setStatus(TestPlanConstants.TEST_PLAN_STATUS_NOT_ARCHIVED); testPlanMapper.insert(testPlan); //测试配置信息 @@ -286,7 +286,7 @@ public class TestPlanBatchOperationService extends TestPlanBaseUtilsService { testPlanGroup.setPos(testPlanGroupService.getNextOrder(originalGroup.getGroupId())); testPlanGroup.setActualEndTime(null); testPlanGroup.setActualStartTime(null); - testPlanGroup.setStatus(TestPlanConstants.TEST_PLAN_STATUS_PREPARED); + testPlanGroup.setStatus(TestPlanConstants.TEST_PLAN_STATUS_NOT_ARCHIVED); testPlanMapper.insert(testPlanGroup); //测试配置信息 diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanBugService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanBugService.java index 0bb0c550ec..784604eef0 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanBugService.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanBugService.java @@ -5,6 +5,7 @@ import io.metersphere.bug.mapper.BugRelationCaseMapper; import io.metersphere.bug.service.BugCommonService; import io.metersphere.plan.dto.TestPlanBugCaseDTO; import io.metersphere.plan.dto.TestPlanCollectionDTO; +import io.metersphere.plan.dto.TestPlanResourceExecResultDTO; import io.metersphere.plan.dto.request.BaseCollectionAssociateRequest; import io.metersphere.plan.dto.request.TestPlanBugPageRequest; import io.metersphere.plan.dto.response.TestPlanBugPageResponse; @@ -62,6 +63,11 @@ public class TestPlanBugService extends TestPlanResourceService { return 0; } + @Override + public List selectDistinctExecResult(String projectId) { + return List.of(); + } + @Override public long getNextOrder(String testPlanId) { diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanFunctionalCaseService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanFunctionalCaseService.java index 044ca7960f..cf074fccae 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanFunctionalCaseService.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanFunctionalCaseService.java @@ -137,6 +137,10 @@ public class TestPlanFunctionalCaseService extends TestPlanResourceService { @Resource private TestPlanApiScenarioMapper testPlanApiScenarioMapper; + @Override + public List selectDistinctExecResult(String projectId) { + return extTestPlanFunctionalCaseMapper.selectDistinctExecResult(projectId); + } @Override public long copyResource(String originalTestPlanId, String newTestPlanId, Map oldCollectionIdToNewCollectionId, String operator, long operatorTime) { List copyList = new ArrayList<>(); diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanManagementService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanManagementService.java index 76e8a80560..71693ad9cd 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanManagementService.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanManagementService.java @@ -5,6 +5,8 @@ import com.github.pagehelper.PageHelper; import io.metersphere.plan.constants.TestPlanResourceConfig; import io.metersphere.plan.domain.TestPlan; import io.metersphere.plan.domain.TestPlanExample; +import io.metersphere.plan.dto.TestPlanGroupCountDTO; +import io.metersphere.plan.dto.TestPlanResourceExecResultDTO; import io.metersphere.plan.dto.request.TestPlanTableRequest; import io.metersphere.plan.dto.response.TestPlanResponse; import io.metersphere.plan.mapper.ExtTestPlanMapper; @@ -23,6 +25,8 @@ import jakarta.annotation.Resource; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.MapUtils; import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -43,6 +47,8 @@ public class TestPlanManagementService { @Resource private TestPlanStatisticsService testPlanStatisticsService; @Resource + private TestPlanBaseUtilsService testPlanBaseUtilsService; + @Resource private TestPlanMapper testPlanMapper; public Map moduleCount(TestPlanTableRequest request) { @@ -69,12 +75,13 @@ public class TestPlanManagementService { return PageUtils.setPageInfo(page, this.list(request)); } + @Autowired + private ApplicationContext applicationContext; private void initDefaultFilter(TestPlanTableRequest request) { + + List defaultStatusList = new ArrayList<>(); + defaultStatusList.add(TestPlanConstants.TEST_PLAN_STATUS_NOT_ARCHIVED); if (request.getFilter() == null || !request.getFilter().containsKey("status")) { - List defaultStatusList = new ArrayList<>(); - defaultStatusList.add(TestPlanConstants.TEST_PLAN_STATUS_PREPARED); - defaultStatusList.add(TestPlanConstants.TEST_PLAN_STATUS_UNDERWAY); - defaultStatusList.add(TestPlanConstants.TEST_PLAN_STATUS_COMPLETED); if (request.getFilter() == null) { request.setFilter(new HashMap<>() {{ this.put("status", defaultStatusList); @@ -82,6 +89,76 @@ public class TestPlanManagementService { } else { request.getFilter().put("status", defaultStatusList); } + } else if (!request.getFilter().get("status").contains(TestPlanConstants.TEST_PLAN_STATUS_ARCHIVED)) { + List statusList = request.getFilter().get("status"); + request.getFilter().put("status", defaultStatusList); + if (statusList.size() < 3) { + List innerIdList = new ArrayList<>(); + // 条件过滤 + Map beansOfType = applicationContext.getBeansOfType(TestPlanResourceService.class); + // 将当前项目下未归档的测试计划结果查询出来,进行下列符合条件的筛选 + List execResults = new ArrayList<>(); + beansOfType.forEach((k, v) -> execResults.addAll(v.selectDistinctExecResult(request.getProjectId()))); + Map>> testPlanExecMap = testPlanBaseUtilsService.parseExecResult(execResults); + Map groupCountMap = extTestPlanMapper.countByGroupPlan(request.getProjectId()) + .stream().collect(Collectors.toMap(TestPlanGroupCountDTO::getGroupId, TestPlanGroupCountDTO::getCount)); + + List completedTestPlanIds = new ArrayList<>(); + List preparedTestPlanIds = new ArrayList<>(); + List underwayTestPlanIds = new ArrayList<>(); + testPlanExecMap.forEach((groupId, planMap) -> { + if (StringUtils.equalsIgnoreCase(groupId, TestPlanConstants.TEST_PLAN_DEFAULT_GROUP_ID)) { + planMap.forEach((planId, resultList) -> { + String result = testPlanBaseUtilsService.calculateTestPlanStatus(resultList); + if (StringUtils.equals(result, TestPlanConstants.TEST_PLAN_SHOW_STATUS_COMPLETED)) { + completedTestPlanIds.add(planId); + } else if (StringUtils.equals(result, TestPlanConstants.TEST_PLAN_SHOW_STATUS_UNDERWAY)) { + underwayTestPlanIds.add(planId); + } else if (StringUtils.equals(result, TestPlanConstants.TEST_PLAN_SHOW_STATUS_PREPARED)) { + preparedTestPlanIds.add(planId); + } + }); + } else { + long itemPlanCount = groupCountMap.getOrDefault(groupId, 0L); + List itemStatusList = new ArrayList<>(); + if (itemPlanCount > planMap.size()) { + // 存在未执行或者没有用例的测试计划。 此时这种测试计划的状态为未开始 + itemStatusList.add(TestPlanConstants.TEST_PLAN_SHOW_STATUS_PREPARED); + } + planMap.forEach((planId, resultList) -> { + itemStatusList.add(testPlanBaseUtilsService.calculateTestPlanStatus(resultList)); + }); + String groupStatus = testPlanBaseUtilsService.calculateStatusByChildren(itemStatusList); + if (StringUtils.equals(groupStatus, TestPlanConstants.TEST_PLAN_SHOW_STATUS_COMPLETED)) { + completedTestPlanIds.add(groupId); + } else if (StringUtils.equals(groupStatus, TestPlanConstants.TEST_PLAN_SHOW_STATUS_UNDERWAY)) { + underwayTestPlanIds.add(groupId); + } else if (StringUtils.equals(groupStatus, TestPlanConstants.TEST_PLAN_SHOW_STATUS_PREPARED)) { + preparedTestPlanIds.add(groupId); + } + } + }); + + testPlanExecMap = null; + if (statusList.contains(TestPlanConstants.TEST_PLAN_SHOW_STATUS_COMPLETED)) { + // 已完成 + innerIdList.addAll(completedTestPlanIds); + } + + if (statusList.contains(TestPlanConstants.TEST_PLAN_SHOW_STATUS_UNDERWAY)) { + // 进行中 + innerIdList.addAll(underwayTestPlanIds); + } + if (statusList.contains(TestPlanConstants.TEST_PLAN_SHOW_STATUS_PREPARED)) { + // 未开始 有一些测试计划/计划组没有用例 / 测试计划, 在上面的计算中无法过滤。所以用排除法机型处理 + List withoutList = new ArrayList<>(); + withoutList.addAll(completedTestPlanIds); + withoutList.addAll(underwayTestPlanIds); + innerIdList.addAll(extTestPlanMapper.selectIdByProjectIdAndWithoutList(request.getProjectId(), withoutList)); + withoutList = null; + } + request.setInnerIds(innerIdList); + } } if (StringUtils.isNotBlank(request.getKeyword())) { diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanResourceService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanResourceService.java index cea7ddf747..3c3c2ff6e8 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanResourceService.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanResourceService.java @@ -5,6 +5,7 @@ import io.metersphere.plan.domain.TestPlanCollectionExample; import io.metersphere.plan.dto.ModuleSelectDTO; import io.metersphere.plan.dto.TestPlanCollectionDTO; import io.metersphere.plan.dto.TestPlanResourceAssociationParam; +import io.metersphere.plan.dto.TestPlanResourceExecResultDTO; import io.metersphere.plan.dto.request.BaseCollectionAssociateRequest; import io.metersphere.plan.dto.request.BasePlanCaseBatchRequest; import io.metersphere.plan.dto.response.TestPlanAssociationResponse; @@ -65,6 +66,7 @@ public abstract class TestPlanResourceService extends TestPlanSortService { public abstract long copyResource(String originalTestPlanId, String newTestPlanId, Map oldCollectionIdToNewCollectionId, String operator, long operatorTime); + public abstract List selectDistinctExecResult(String projectId); /** * 关联用例 * @@ -124,6 +126,4 @@ public abstract class TestPlanResourceService extends TestPlanSortService { AssociateCaseDTO associateCaseDTO = new AssociateCaseDTO(excludeIds, selectIds, moduleIds); return associateCaseDTO; } - - } diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanSendNoticeService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanSendNoticeService.java index 5a549bb80d..df7636e33f 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanSendNoticeService.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanSendNoticeService.java @@ -134,7 +134,7 @@ public class TestPlanSendNoticeService { public TestPlanDTO sendAddNotice(TestPlanCreateRequest request) { TestPlanDTO dto = new TestPlanDTO(); BeanUtils.copyBean(dto, request); - dto.setStatus(TestPlanConstants.TEST_PLAN_STATUS_PREPARED); + dto.setStatus(TestPlanConstants.TEST_PLAN_SHOW_STATUS_PREPARED); return dto; } diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanService.java index 77c76803e9..7d7aa77c25 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanService.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanService.java @@ -6,6 +6,7 @@ import io.metersphere.plan.dto.TestPlanExecuteHisDTO; import io.metersphere.plan.dto.request.*; import io.metersphere.plan.dto.response.TestPlanDetailResponse; import io.metersphere.plan.dto.response.TestPlanOperationResponse; +import io.metersphere.plan.dto.response.TestPlanStatisticsResponse; import io.metersphere.plan.enums.ExecuteMethod; import io.metersphere.plan.job.TestPlanScheduleJob; import io.metersphere.plan.mapper.*; @@ -102,7 +103,7 @@ public class TestPlanService extends TestPlanBaseUtilsService { private static final int MAX_TAG_SIZE = 10; - @Autowired + @Resource private TestPlanReportService testPlanReportService; /** @@ -151,7 +152,7 @@ public class TestPlanService extends TestPlanBaseUtilsService { createTestPlan.setUpdateUser(operator); createTestPlan.setCreateTime(operateTime); createTestPlan.setUpdateTime(operateTime); - createTestPlan.setStatus(TestPlanConstants.TEST_PLAN_STATUS_PREPARED); + createTestPlan.setStatus(TestPlanConstants.TEST_PLAN_STATUS_NOT_ARCHIVED); TestPlanConfig testPlanConfig = new TestPlanConfig(); testPlanConfig.setTestPlanId(createTestPlan.getId()); @@ -395,18 +396,20 @@ public class TestPlanService extends TestPlanBaseUtilsService { /** * 测试计划归档 - * - * @param id - * @param userId */ public void archived(String id, String userId) { TestPlan testPlan = testPlanMapper.selectByPrimaryKey(id); + if (StringUtils.equalsAnyIgnoreCase(testPlan.getType(), TestPlanConstants.TEST_PLAN_TYPE_GROUP)) { + //判断当前计划组下是否都已完成 (由于算法原因,只需要校验当前测试计划组即可) + if (!this.isTestPlanCompleted(id)) { + throw new MSException(Translator.get("test_plan.group.not_plan")); + } //测试计划组归档 - updateGroupStatus(testPlan.getId(), userId); + updateCompletedGroupStatus(testPlan.getId(), userId); //关闭定时任务 this.deleteScheduleConfig(testPlan.getId()); - } else if (StringUtils.equals(testPlan.getStatus(), TestPlanConstants.TEST_PLAN_STATUS_COMPLETED) && StringUtils.equalsIgnoreCase(testPlan.getGroupId(), TestPlanConstants.TEST_PLAN_DEFAULT_GROUP_ID)) { + } else if (this.isTestPlanCompleted(id) && StringUtils.equalsIgnoreCase(testPlan.getGroupId(), TestPlanConstants.TEST_PLAN_DEFAULT_GROUP_ID)) { //测试计划 testPlan.setStatus(TestPlanConstants.TEST_PLAN_STATUS_ARCHIVED); testPlan.setUpdateUser(userId); @@ -417,7 +420,37 @@ public class TestPlanService extends TestPlanBaseUtilsService { } else { throw new MSException(Translator.get("test_plan.cannot.archived")); } + } + /** + * 测试计划归档 + */ + public boolean archived(TestPlan testPlan, String userId) { + if (!this.isTestPlanCompleted(testPlan.getId())) { + return false; + } + if (StringUtils.equalsAnyIgnoreCase(testPlan.getType(), TestPlanConstants.TEST_PLAN_TYPE_GROUP)) { + //测试计划组归档 + updateCompletedGroupStatus(testPlan.getId(), userId); + //关闭定时任务 + this.deleteScheduleConfig(testPlan.getId()); + return true; + } else if (StringUtils.equalsIgnoreCase(testPlan.getGroupId(), TestPlanConstants.TEST_PLAN_DEFAULT_GROUP_ID)) { + //测试计划 + testPlan.setStatus(TestPlanConstants.TEST_PLAN_STATUS_ARCHIVED); + testPlan.setUpdateUser(userId); + testPlan.setUpdateTime(System.currentTimeMillis()); + testPlanMapper.updateByPrimaryKeySelective(testPlan); + //关闭定时任务 + this.deleteScheduleConfig(testPlan.getId()); + return true; + } + return false; + } + + public boolean isTestPlanCompleted(String testPlanId) { + TestPlanStatisticsResponse statisticsResponse = testPlanStatisticsService.calculateRate(Collections.singletonList(testPlanId)).getFirst(); + return StringUtils.equalsIgnoreCase(statisticsResponse.getStatus(), TestPlanConstants.TEST_PLAN_SHOW_STATUS_COMPLETED); } /** @@ -430,14 +463,20 @@ public class TestPlanService extends TestPlanBaseUtilsService { List batchArchivedIds = request.getSelectIds(); if (CollectionUtils.isNotEmpty(batchArchivedIds)) { TestPlanExample example = new TestPlanExample(); - example.createCriteria().andIdIn(batchArchivedIds); - List archivedPlanList = testPlanMapper.selectByExample(example).stream().filter( + example.createCriteria().andIdIn(batchArchivedIds).andStatusNotEqualTo(TestPlanConstants.TEST_PLAN_STATUS_ARCHIVED); + List testPlanList = testPlanMapper.selectByExample(example).stream().filter( testPlan -> StringUtils.equalsAnyIgnoreCase(testPlan.getType(), TestPlanConstants.TEST_PLAN_TYPE_GROUP) - || (StringUtils.equals(testPlan.getStatus(), TestPlanConstants.TEST_PLAN_STATUS_COMPLETED) && StringUtils.equalsIgnoreCase(testPlan.getGroupId(), TestPlanConstants.TEST_PLAN_DEFAULT_GROUP_ID)) - ).collect(Collectors.toList()); - archivedPlanList.forEach(item -> this.archived(item.getId(), currentUser)); + || (StringUtils.equalsIgnoreCase(testPlan.getGroupId(), TestPlanConstants.TEST_PLAN_DEFAULT_GROUP_ID)) + ).toList(); + List archivedPlanGroupList = new ArrayList<>(); + testPlanList.forEach(item -> { + boolean result = this.archived(item, currentUser); + if (result) { + archivedPlanGroupList.add(item); + } + }); //日志 - testPlanLogService.saveBatchLog(archivedPlanList, currentUser, "/test-plan/batch-archived", HttpMethodConstants.POST.name(), OperationLogType.ARCHIVED.name(), "archive"); + testPlanLogService.saveBatchLog(archivedPlanGroupList, currentUser, "/test-plan/batch-archived", HttpMethodConstants.POST.name(), OperationLogType.ARCHIVED.name(), "archive"); } } @@ -447,17 +486,14 @@ public class TestPlanService extends TestPlanBaseUtilsService { * @param id * @param userId */ - private void updateGroupStatus(String id, String userId) { + private void updateCompletedGroupStatus(String id, String userId) { TestPlanExample example = new TestPlanExample(); example.createCriteria().andGroupIdEqualTo(id); List testPlanList = testPlanMapper.selectByExample(example); if (CollectionUtils.isEmpty(testPlanList)) { throw new MSException(Translator.get("test_plan.group.not_plan")); } - List ids = testPlanList.stream().filter(item -> StringUtils.equalsIgnoreCase(item.getStatus(), TestPlanConstants.TEST_PLAN_STATUS_COMPLETED)).map(TestPlan::getId).collect(Collectors.toList()); - if (CollectionUtils.isEmpty(ids)) { - throw new MSException(Translator.get("test_plan.group.not_plan")); - } + List ids = testPlanList.stream().map(TestPlan::getId).collect(Collectors.toList()); ids.add(id); extTestPlanMapper.batchUpdateStatus(TestPlanConstants.TEST_PLAN_STATUS_ARCHIVED, userId, System.currentTimeMillis(), ids); } @@ -772,37 +808,6 @@ public class TestPlanService extends TestPlanBaseUtilsService { } } - - // private void updateTestPlanStatus(String testPlanId) { - // Map caseExecResultCount = new HashMap<>(); - // Map beansOfType = applicationContext.getBeansOfType(TestPlanResourceService.class); - // beansOfType.forEach((k, v) -> { - // Map map = v.caseExecResultCount(testPlanId); - // map.forEach((key, value) -> { - // if (value != 0) { - // caseExecResultCount.merge(key, value, Long::sum); - // } - // }); - // }); - // - // String testPlanFinalStatus = TestPlanConstants.TEST_PLAN_STATUS_UNDERWAY; - // if (MapUtils.isEmpty(caseExecResultCount)) { - // // 没有任何执行结果: 状态是未开始 - // testPlanFinalStatus = TestPlanConstants.TEST_PLAN_STATUS_PREPARED; - // extTestPlanMapper.clearActualEndTime(testPlanId); - // } else if (caseExecResultCount.size() == 1 && caseExecResultCount.containsKey(ExecStatus.PENDING.name()) && caseExecResultCount.get(ExecStatus.PENDING.name()) > 0) { - // // 执行结果只有未开始: 状态是未开始 - // testPlanFinalStatus = TestPlanConstants.TEST_PLAN_STATUS_PREPARED; - // extTestPlanMapper.clearActualEndTime(testPlanId); - // } else if (!caseExecResultCount.containsKey(ExecStatus.PENDING.name())) { - // // 执行结果没有未开始: 已完成 - // testPlanFinalStatus = TestPlanConstants.TEST_PLAN_STATUS_COMPLETED; - // extTestPlanMapper.setActualEndTime(testPlanId, System.currentTimeMillis()); - // } - // - // this.updateTestPlanStatusAndGroupStatus(testPlanId, testPlanFinalStatus); - // } - public TestPlanOperationResponse sort(PosRequest request, LogInsertModule logInsertModule) { testPlanGroupService.sort(request); testPlanLogService.saveMoveLog(testPlanMapper.selectByPrimaryKey(request.getMoveId()), request.getMoveId(), logInsertModule); diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanStatisticsService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanStatisticsService.java index d1db3abd41..04b4fa700e 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanStatisticsService.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanStatisticsService.java @@ -4,7 +4,6 @@ import io.metersphere.plan.domain.*; import io.metersphere.plan.dto.response.TestPlanBugPageResponse; import io.metersphere.plan.dto.response.TestPlanStatisticsResponse; import io.metersphere.plan.mapper.*; -import io.metersphere.plan.utils.RateCalculateUtils; import io.metersphere.sdk.constants.ExecStatus; import io.metersphere.sdk.constants.ResultStatus; import io.metersphere.sdk.constants.ScheduleResourceType; @@ -16,6 +15,7 @@ import io.metersphere.system.mapper.ScheduleMapper; import io.metersphere.system.utils.ScheduleUtils; import jakarta.annotation.Resource; import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; import java.util.*; @@ -41,6 +41,8 @@ public class TestPlanStatisticsService { private ExtTestPlanBugMapper extTestPlanBugMapper; @Resource private ScheduleMapper scheduleMapper; + @Resource + private TestPlanBaseUtilsService testPlanBaseUtilsService; /** * 计划/计划组的用例统计数据 @@ -71,92 +73,160 @@ public class TestPlanStatisticsService { }); } - /** - * 计划/计划组的{通过率, 执行进度}统计数据 - * - * @param planIds 计划ID集合 - */ - public List calculateRate(List planIds) { - // 查出子计划 - TestPlanExample testPlanExample = new TestPlanExample(); - testPlanExample.createCriteria().andGroupIdIn(planIds); - List childrenPlan = testPlanMapper.selectByExample(testPlanExample); - childrenPlan.forEach(item -> planIds.add(item.getId())); - - List planStatisticsResponses = new ArrayList<>(); - /* - * 1. 查询计划下的用例数据集合 - * 2. 根据执行结果统计(结果小数保留两位) - */ - // 计划的更多配置 + private Map selectConfig(List testPlanIds) { TestPlanConfigExample example = new TestPlanConfigExample(); - example.createCriteria().andTestPlanIdIn(planIds); + example.createCriteria().andTestPlanIdIn(testPlanIds); List testPlanConfigList = testPlanConfigMapper.selectByExample(example); - Map planConfigMap = testPlanConfigList.stream().collect(Collectors.toMap(TestPlanConfig::getTestPlanId, p -> p)); - // 关联的用例数据 - Map> planFunctionalCaseMap = getFunctionalCaseMapByPlanIds(planIds); - Map> planApiCaseMap = getApiCaseMapByPlanIds(planIds); - Map> planApiScenarioMap = getApiScenarioByPlanIds(planIds); + return testPlanConfigList.stream().collect(Collectors.toMap(TestPlanConfig::getTestPlanId, p -> p)); + } - //查询定时任务 + private Map selectSchedule(List testPlanIds) { ScheduleExample scheduleExample = new ScheduleExample(); - scheduleExample.createCriteria().andResourceIdIn(planIds).andResourceTypeEqualTo(ScheduleResourceType.TEST_PLAN.name()); + scheduleExample.createCriteria().andResourceIdIn(testPlanIds).andResourceTypeEqualTo(ScheduleResourceType.TEST_PLAN.name()); List schedules = scheduleMapper.selectByExample(scheduleExample); - Map scheduleMap = schedules.stream().collect(Collectors.toMap(Schedule::getResourceId, t -> t)); + return schedules.stream().collect(Collectors.toMap(Schedule::getResourceId, t -> t)); + } - planIds.forEach(planId -> { - TestPlanStatisticsResponse statisticsResponse = new TestPlanStatisticsResponse(); - statisticsResponse.setId(planId); + /** + * 计划/计划组的{通过率, 执行进度, 状态}统计数据 + * + */ + public List calculateRate(List paramIds) { + // 查出所有计划 + TestPlanExample testPlanExample = new TestPlanExample(); + testPlanExample.createCriteria().andIdIn(paramIds); + List paramTestPlanList = testPlanMapper.selectByExample(testPlanExample); + testPlanExample.clear(); + testPlanExample.createCriteria().andGroupIdIn(paramIds); + List childrenTestPlan = testPlanMapper.selectByExample(testPlanExample); + paramTestPlanList.removeAll(childrenTestPlan); + List allTestPlanIdList = new ArrayList<>(); + allTestPlanIdList.addAll(paramTestPlanList.stream().map(TestPlan::getId).toList()); + allTestPlanIdList.addAll(childrenTestPlan.stream().map(TestPlan::getId).toList()); - // 测试计划组没有测试计划配置。同理,也不用参与用例等数据的计算 - if (planConfigMap.containsKey(planId)) { - statisticsResponse.setPassThreshold(planConfigMap.get(planId).getPassThreshold()); - // 功能用例分组统计开始 (为空时, 默认为未执行) - List functionalCases = planFunctionalCaseMap.get(planId); - statisticsResponse.setFunctionalCaseCount(CollectionUtils.isNotEmpty(functionalCases) ? functionalCases.size() : 0); - Map functionalCaseResultCountMap = CollectionUtils.isEmpty(functionalCases) ? new HashMap<>(16) : functionalCases.stream().collect( - Collectors.groupingBy(functionalCase -> Optional.ofNullable(functionalCase.getLastExecResult()).orElse(ExecStatus.PENDING.name()), Collectors.counting())); - // 接口用例分组统计开始 (为空时, 默认为未执行) - List apiCases = planApiCaseMap.get(planId); - statisticsResponse.setApiCaseCount(CollectionUtils.isNotEmpty(apiCases) ? apiCases.size() : 0); - Map apiCaseResultCountMap = CollectionUtils.isEmpty(apiCases) ? new HashMap<>(16) : apiCases.stream().collect( - Collectors.groupingBy(apiCase -> Optional.ofNullable(apiCase.getLastExecResult()).orElse(ExecStatus.PENDING.name()), Collectors.counting())); - // 接口场景用例分组统计开始 (为空时, 默认为未执行) - List apiScenarios = planApiScenarioMap.get(planId); - statisticsResponse.setApiScenarioCount(CollectionUtils.isNotEmpty(apiScenarios) ? apiScenarios.size() : 0); - Map apiScenarioResultCountMap = CollectionUtils.isEmpty(apiScenarios) ? new HashMap<>(16) : apiScenarios.stream().collect( - Collectors.groupingBy(apiScenario -> Optional.ofNullable(apiScenario.getLastExecResult()).orElse(ExecStatus.PENDING.name()), Collectors.counting())); - // 用例数据汇总 - statisticsResponse.setSuccessCount(countCaseMap(functionalCaseResultCountMap, apiCaseResultCountMap, apiScenarioResultCountMap, ResultStatus.SUCCESS.name())); - statisticsResponse.setErrorCount(countCaseMap(functionalCaseResultCountMap, apiCaseResultCountMap, apiScenarioResultCountMap, ResultStatus.ERROR.name())); - statisticsResponse.setFakeErrorCount(countCaseMap(functionalCaseResultCountMap, apiCaseResultCountMap, apiScenarioResultCountMap, ResultStatus.FAKE_ERROR.name())); - statisticsResponse.setBlockCount(countCaseMap(functionalCaseResultCountMap, apiCaseResultCountMap, apiScenarioResultCountMap, ResultStatus.BLOCKED.name())); - statisticsResponse.setPendingCount(countCaseMap(functionalCaseResultCountMap, apiCaseResultCountMap, apiScenarioResultCountMap, ExecStatus.PENDING.name())); - statisticsResponse.setCaseTotal(statisticsResponse.getFunctionalCaseCount() + statisticsResponse.getApiCaseCount() + statisticsResponse.getApiScenarioCount()); - // 通过率 {通过用例数/总用例数} && 执行进度 {非未执行的用例数/总用例数} - statisticsResponse.setPassRate(RateCalculateUtils.divWithPrecision(statisticsResponse.getSuccessCount(), statisticsResponse.getCaseTotal(), 2)); - statisticsResponse.setExecuteRate(RateCalculateUtils.divWithPrecision(statisticsResponse.getCaseTotal() - statisticsResponse.getPendingCount(), statisticsResponse.getCaseTotal(), 2)); - } - planStatisticsResponses.add(statisticsResponse); - - //定时任务 - if (scheduleMap.containsKey(planId)) { - Schedule schedule = scheduleMap.get(planId); - BaseScheduleConfigRequest request = new BaseScheduleConfigRequest(); - request.setEnable(schedule.getEnable()); - request.setCron(schedule.getValue()); - request.setResourceId(planId); - if (schedule.getConfig() != null) { - request.setRunConfig(JSON.parseObject(schedule.getConfig(), Map.class)); - } - statisticsResponse.setScheduleConfig(request); - if (schedule.getEnable()) { - statisticsResponse.setNextTriggerTime(ScheduleUtils.getNextTriggerTime(schedule.getValue())); + Map> groupTestPlanMap = new HashMap<>(); + for (TestPlan testPlan : paramTestPlanList) { + List children = new ArrayList<>(); + for (TestPlan child : childrenTestPlan) { + if (StringUtils.equalsIgnoreCase(child.getGroupId(), testPlan.getId())) { + children.add(child); } } + groupTestPlanMap.put(testPlan, children); + childrenTestPlan.removeAll(children); + } + childrenTestPlan = null; + paramTestPlanList = null; + List returnResponse = new ArrayList<>(); + + // 计划的更多配置 + Map planConfigMap = this.selectConfig(allTestPlanIdList); + // 关联的用例数据 + Map> planFunctionalCaseMap = getFunctionalCaseMapByPlanIds(allTestPlanIdList); + Map> planApiCaseMap = getApiCaseMapByPlanIds(allTestPlanIdList); + Map> planApiScenarioMap = getApiScenarioByPlanIds(allTestPlanIdList); + //查询定时任务 + Map scheduleMap = this.selectSchedule(allTestPlanIdList); + + groupTestPlanMap.forEach((rootPlan, children) -> { + TestPlanStatisticsResponse rootResponse = this.genTestPlanStatisticsResponse(rootPlan, planConfigMap, planFunctionalCaseMap, planApiCaseMap, planApiScenarioMap, scheduleMap); + List childrenResponse = new ArrayList<>(); + if (!CollectionUtils.isEmpty(children)) { + List childStatus = new ArrayList<>(); + children.forEach(child -> { + TestPlanStatisticsResponse childResponse = this.genTestPlanStatisticsResponse(child, planConfigMap, planFunctionalCaseMap, planApiCaseMap, planApiScenarioMap, scheduleMap); + childResponse.calculateStatus(); + childStatus.add(childResponse.getStatus()); + //添加到rootResponse中 + rootResponse.calculateAllNumber(childResponse); + childrenResponse.add(childResponse); + }); + rootResponse.calculateCaseTotal(); + rootResponse.calculatePassRate(); + rootResponse.calculateExecuteRate(); + rootResponse.setStatus(testPlanBaseUtilsService.calculateStatusByChildren(childStatus)); + } else { + rootResponse.calculateCaseTotal(); + rootResponse.calculatePassRate(); + rootResponse.calculateExecuteRate(); + rootResponse.calculateStatus(); + } + returnResponse.add(rootResponse); + returnResponse.addAll(childrenResponse); }); - return planStatisticsResponses; + return returnResponse; + } + + private Map countApiScenarioExecResultMap(List apiScenarios) { + return CollectionUtils.isEmpty(apiScenarios) ? new HashMap<>(16) : apiScenarios.stream().collect( + Collectors.groupingBy(apiScenario -> Optional.ofNullable(apiScenario.getLastExecResult()).orElse(ExecStatus.PENDING.name()), Collectors.counting())); + } + + private Map countApiTestCaseExecResultMap(List apiCases) { + return CollectionUtils.isEmpty(apiCases) ? new HashMap<>(16) : apiCases.stream().collect( + Collectors.groupingBy(apiCase -> Optional.ofNullable(apiCase.getLastExecResult()).orElse(ExecStatus.PENDING.name()), Collectors.counting())); + } + + private Map countFunctionalCaseExecResultMap(List functionalCases) { + return CollectionUtils.isEmpty(functionalCases) ? new HashMap<>(16) : functionalCases.stream().collect( + Collectors.groupingBy(functionalCase -> Optional.ofNullable(functionalCase.getLastExecResult()).orElse(ExecStatus.PENDING.name()), Collectors.counting())); + } + + private TestPlanStatisticsResponse genTestPlanStatisticsResponse(TestPlan child, + Map planConfigMap, + Map> planFunctionalCaseMap, + Map> planApiCaseMap, + Map> planApiScenarioMap, + Map scheduleMap) { + String planId = child.getId(); + TestPlanStatisticsResponse statisticsResponse = new TestPlanStatisticsResponse(); + statisticsResponse.setId(planId); + // 测试计划组没有测试计划配置。同理,也不用参与用例等数据的计算 + if (planConfigMap.containsKey(planId)) { + statisticsResponse.setPassThreshold(planConfigMap.get(planId).getPassThreshold()); + + List functionalCases = planFunctionalCaseMap.get(planId); + List apiCases = planApiCaseMap.get(planId); + List apiScenarios = planApiScenarioMap.get(planId); + + + // 功能用例分组统计开始 (为空时, 默认为未执行) + Map functionalCaseResultCountMap = this.countFunctionalCaseExecResultMap(functionalCases); + // 接口用例分组统计开始 (为空时, 默认为未执行) + Map apiCaseResultCountMap = this.countApiTestCaseExecResultMap(apiCases); + // 接口场景用例分组统计开始 (为空时, 默认为未执行) + Map apiScenarioResultCountMap = this.countApiScenarioExecResultMap(apiScenarios); + + // 用例数据汇总 + statisticsResponse.setFunctionalCaseCount(CollectionUtils.isNotEmpty(functionalCases) ? functionalCases.size() : 0); + statisticsResponse.setApiCaseCount(CollectionUtils.isNotEmpty(apiCases) ? apiCases.size() : 0); + statisticsResponse.setApiScenarioCount(CollectionUtils.isNotEmpty(apiScenarios) ? apiScenarios.size() : 0); + statisticsResponse.setSuccessCount(countCaseMap(functionalCaseResultCountMap, apiCaseResultCountMap, apiScenarioResultCountMap, ResultStatus.SUCCESS.name())); + statisticsResponse.setErrorCount(countCaseMap(functionalCaseResultCountMap, apiCaseResultCountMap, apiScenarioResultCountMap, ResultStatus.ERROR.name())); + statisticsResponse.setFakeErrorCount(countCaseMap(functionalCaseResultCountMap, apiCaseResultCountMap, apiScenarioResultCountMap, ResultStatus.FAKE_ERROR.name())); + statisticsResponse.setBlockCount(countCaseMap(functionalCaseResultCountMap, apiCaseResultCountMap, apiScenarioResultCountMap, ResultStatus.BLOCKED.name())); + statisticsResponse.setPendingCount(countCaseMap(functionalCaseResultCountMap, apiCaseResultCountMap, apiScenarioResultCountMap, ExecStatus.PENDING.name())); + statisticsResponse.calculateCaseTotal(); + statisticsResponse.calculatePassRate(); + statisticsResponse.calculateExecuteRate(); + } + //定时任务 + if (scheduleMap.containsKey(planId)) { + Schedule schedule = scheduleMap.get(planId); + BaseScheduleConfigRequest request = new BaseScheduleConfigRequest(); + request.setEnable(schedule.getEnable()); + request.setCron(schedule.getValue()); + request.setResourceId(planId); + if (schedule.getConfig() != null) { + request.setRunConfig(JSON.parseObject(schedule.getConfig(), Map.class)); + } + statisticsResponse.setScheduleConfig(request); + if (schedule.getEnable()) { + statisticsResponse.setNextTriggerTime(ScheduleUtils.getNextTriggerTime(schedule.getValue())); + } + } + return statisticsResponse; } diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/utils/RateCalculateUtils.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/utils/RateCalculateUtils.java index 7b2335005b..bcf33d85fd 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/utils/RateCalculateUtils.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/utils/RateCalculateUtils.java @@ -20,7 +20,7 @@ public class RateCalculateUtils { * @param precision 精度 * @return rate */ - public static Double divWithPrecision(Integer molecular, Integer denominator, Integer precision) { + public static Double divWithPrecision(Long molecular, Long denominator, Integer precision) { DecimalFormat rateFormat = new DecimalFormat("#.##"); rateFormat.setMinimumFractionDigits(precision); rateFormat.setMaximumFractionDigits(precision); @@ -38,4 +38,8 @@ public class RateCalculateUtils { } return rate; } + + public static Double divWithPrecision(Integer molecular, Integer denominator, Integer precision) { + return divWithPrecision((long) molecular, (long) denominator, precision); + } } diff --git a/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanTests.java b/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanTests.java index ca0a8451c6..70b53b2146 100644 --- a/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanTests.java +++ b/backend/services/test-plan/src/test/java/io/metersphere/plan/controller/TestPlanTests.java @@ -3,11 +3,9 @@ package io.metersphere.plan.controller; import io.metersphere.api.domain.ApiScenario; import io.metersphere.api.domain.ApiTestCase; import io.metersphere.functional.domain.FunctionalCase; +import io.metersphere.functional.mapper.FunctionalCaseMapper; import io.metersphere.plan.constants.TestPlanResourceConfig; -import io.metersphere.plan.domain.TestPlan; -import io.metersphere.plan.domain.TestPlanConfig; -import io.metersphere.plan.domain.TestPlanExample; -import io.metersphere.plan.domain.TestPlanReport; +import io.metersphere.plan.domain.*; import io.metersphere.plan.dto.request.*; import io.metersphere.plan.dto.response.TestPlanOperationResponse; import io.metersphere.plan.dto.response.TestPlanResponse; @@ -21,10 +19,7 @@ import io.metersphere.plan.utils.TestPlanTestUtils; import io.metersphere.project.domain.Project; import io.metersphere.project.dto.filemanagement.request.FileModuleCreateRequest; import io.metersphere.project.dto.filemanagement.request.FileModuleUpdateRequest; -import io.metersphere.sdk.constants.ModuleConstants; -import io.metersphere.sdk.constants.PermissionConstants; -import io.metersphere.sdk.constants.SessionConstants; -import io.metersphere.sdk.constants.TestPlanConstants; +import io.metersphere.sdk.constants.*; import io.metersphere.sdk.util.BeanUtils; import io.metersphere.sdk.util.CommonBeanFactory; import io.metersphere.sdk.util.JSON; @@ -43,6 +38,7 @@ import io.metersphere.system.log.constants.OperationLogType; import io.metersphere.system.mapper.TestPlanModuleMapper; import io.metersphere.system.service.CommonProjectService; import io.metersphere.system.uid.IDGenerator; +import io.metersphere.system.uid.NumGenerator; import io.metersphere.system.utils.CheckLogModel; import io.metersphere.system.utils.Pager; import jakarta.annotation.Resource; @@ -138,9 +134,13 @@ public class TestPlanTests extends BaseTest { private static final String URL_TEST_PLAN_BATCH_ARCHIVED = "/test-plan/batch-archived"; private static final String URL_TEST_PLAN_BATCH_EDIT = "/test-plan/batch-edit"; + private static String testPlanId6 = null; + private static String testPlanId16 = null; private static String groupTestPlanId7 = null; private static String groupTestPlanId15 = null; private static String groupTestPlanId35 = null; + private static String groupTestPlanId45 = null; + private static String groupTestPlanId46 = null; private static List rootPlanIds = new ArrayList<>(); @@ -154,6 +154,10 @@ public class TestPlanTests extends BaseTest { private ExtTestPlanMapper extTestPlanMapper; @Resource private TestPlanFunctionalCaseMapper testPlanFunctionalCaseMapper; + @Resource + private FunctionalCaseMapper functionalCaseMapper; + + private static List functionalCaseId = new ArrayList<>(); @BeforeEach public void initTestData() { @@ -542,7 +546,7 @@ public class TestPlanTests extends BaseTest { String moduleId; if (i < 50) { moduleId = a1Node.getId(); - if (i == 7 || i == 15 || i == 35) { + if (i == 7 || i == 15 || i == 35 || i == 45 || i == 46) { request.setType(TestPlanConstants.TEST_PLAN_TYPE_GROUP); } a1NodeCount++; @@ -574,13 +578,20 @@ public class TestPlanTests extends BaseTest { ResultHolder holder = JSON.parseObject(returnStr, ResultHolder.class); String returnId = JSON.parseObject(JSON.toJSONString(holder.getData()), TestPlan.class).getId(); Assertions.assertNotNull(returnId); - - if (i == 7) { + if (i == 6) { + testPlanId6 = returnId; + } else if (i == 7) { groupTestPlanId7 = returnId; } else if (i == 15) { groupTestPlanId15 = returnId; + } else if (i == 16) { + testPlanId16 = returnId; } else if (i == 35) { groupTestPlanId35 = returnId; + } else if (i == 45) { + groupTestPlanId45 = returnId; + } else if (i == 46) { + groupTestPlanId46 = returnId; } else if (i > 700 && i < 725) { // 701-749 要创建测试计划报告 每个测试计划创建250个报告 SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH); @@ -676,6 +687,32 @@ public class TestPlanTests extends BaseTest { } } + // testPlanId6关联两条已完成用例 + this.associationFuncCase(testPlanId6, true); + // testPlanId16 关联未开始用例 + this.associationFuncCase(testPlanId16, false); + //在groupTestPlanId35 和 groupTestPlanId46下面各创建2条测试计划并关联已完成用例 + for (int i = 0; i < 4; i++) { + TestPlanCreateRequest itemRequest = new TestPlanCreateRequest(); + itemRequest.setProjectId(project.getId()); + itemRequest.setModuleId(a1Node.getId()); + if (i > 2) { + itemRequest.setGroupId(groupTestPlanId46); + itemRequest.setName("testPlan_group46_" + i); + } else { + itemRequest.setGroupId(groupTestPlanId35); + itemRequest.setName("testPlan_group35_" + i); + } + itemRequest.setBaseAssociateCaseRequest(associateCaseRequest); + MvcResult mvcResult = this.requestPostWithOkAndReturn(URL_POST_TEST_PLAN_ADD, itemRequest); + String returnStr = mvcResult.getResponse().getContentAsString(); + ResultHolder holder = JSON.parseObject(returnStr, ResultHolder.class); + Assertions.assertNotNull(JSON.parseObject(JSON.toJSONString(holder.getData()), TestPlan.class).getId()); + String returnId = JSON.parseObject(JSON.toJSONString(holder.getData()), TestPlan.class).getId(); + this.associationFuncCase(returnId, true); + } + + //校验Group数量 List groupList = JSON.parseArray( JSON.toJSONString( @@ -683,7 +720,7 @@ public class TestPlanTests extends BaseTest { this.requestGetWithOkAndReturn(String.format(URL_POST_TEST_PLAN_GROUP_LIST, project.getId())) .getResponse().getContentAsString(), ResultHolder.class).getData()), TestPlan.class); - Assertions.assertEquals(groupList.size(), 3); + Assertions.assertEquals(groupList.size(), 5); /* 反例 @@ -717,7 +754,49 @@ public class TestPlanTests extends BaseTest { this.checkTestPlanSortWithOutGroup(); this.checkTestPlanSortInGroup(groupTestPlanId7); this.checkTestPlanMoveToGroup(); - this.checkTestPlanGroupArchived(groupTestPlanId7); + this.checkTestPlanGroupArchived(groupTestPlanId35); + } + + private void associationFuncCase(String testPlanId, boolean isFinish) { + FunctionalCase functionalCase = new FunctionalCase(); + functionalCase.setProjectId(project.getId()); + functionalCase.setName(String.valueOf(System.currentTimeMillis())); + functionalCase.setId(IDGenerator.nextStr()); + functionalCase.setModuleId("root"); + functionalCase.setTemplateId("root"); + functionalCase.setReviewStatus("PREPARED"); + functionalCase.setCaseEditType("STEP"); + functionalCase.setPos(4096L); + functionalCase.setVersionId("root"); + functionalCase.setRefId(functionalCase.getId()); + functionalCase.setLastExecuteResult("PREPARED"); + functionalCase.setDeleted(false); + functionalCase.setPublicCase(false); + functionalCase.setLatest(true); + functionalCase.setCreateUser("admin"); + functionalCase.setCreateTime(System.currentTimeMillis()); + functionalCase.setUpdateTime(System.currentTimeMillis()); + functionalCase.setNum(NumGenerator.nextNum(project.getId(), ApplicationNumScope.CASE_MANAGEMENT)); + functionalCaseMapper.insert(functionalCase); + functionalCaseId.add(functionalCase.getId()); + + TestPlanFunctionalCase testPlanFunctionalCase = new TestPlanFunctionalCase(); + testPlanFunctionalCase.setId(IDGenerator.nextStr()); + testPlanFunctionalCase.setTestPlanId(testPlanId); + testPlanFunctionalCase.setFunctionalCaseId(functionalCase.getId()); + testPlanFunctionalCase.setCreateTime(System.currentTimeMillis()); + testPlanFunctionalCase.setCreateUser("admin"); + testPlanFunctionalCase.setPos(4096L); + testPlanFunctionalCase.setLastExecResult("SUCCESS"); + testPlanFunctionalCase.setTestPlanCollectionId("root"); + testPlanFunctionalCaseMapper.insert(testPlanFunctionalCase); + + if (!isFinish) { + testPlanFunctionalCase.setId(IDGenerator.nextStr()); + testPlanFunctionalCase.setPos(8192L); + testPlanFunctionalCase.setLastExecResult("PENDING"); + testPlanFunctionalCaseMapper.insert(testPlanFunctionalCase); + } } private List selectByGroupId(String groupId) throws Exception { @@ -912,16 +991,16 @@ public class TestPlanTests extends BaseTest { //判断组归档 TestPlan updatePlan = new TestPlan(); - updatePlan.setId(groupTestPlanId35); + updatePlan.setId(groupTestPlanId45); updatePlan.setStatus(TestPlanConstants.TEST_PLAN_STATUS_ARCHIVED); testPlanMapper.updateByPrimaryKeySelective(updatePlan); this.requestPost(URL_TEST_PLAN_BATCH_MOVE, request).andExpect(status().is5xxServerError()); //改回来 - updatePlan.setStatus(TestPlanConstants.TEST_PLAN_STATUS_PREPARED); + updatePlan.setStatus(TestPlanConstants.TEST_PLAN_STATUS_NOT_ARCHIVED); testPlanMapper.updateByPrimaryKeySelective(updatePlan); //正式测试 - groupId = groupTestPlanId35; + groupId = groupTestPlanId45; request.setTargetId(groupId); this.requestPostWithOkAndReturn(URL_TEST_PLAN_BATCH_MOVE, request); List groups = this.selectByGroupId(groupId); @@ -950,20 +1029,21 @@ public class TestPlanTests extends BaseTest { // 测试计划组内的测试计划不能归档 List testPlanResponseList = this.selectByGroupId(groupId); TestPlanResponse cannotArchivedPlan = testPlanResponseList.getFirst(); - testPlanMapper.updateByPrimaryKeySelective(new TestPlan() {{ - this.setId(cannotArchivedPlan.getId()); - this.setStatus(TestPlanConstants.TEST_PLAN_STATUS_COMPLETED); - }}); this.requestGet(String.format(URL_TEST_PLAN_ARCHIVED, cannotArchivedPlan.getId())).andExpect(status().is5xxServerError()); //归档测试组内的测试计划 - for (TestPlanResponse testPlanResponse : testPlanResponseList) { - testPlanMapper.updateByPrimaryKeySelective(new TestPlan() {{ - this.setId(testPlanResponse.getId()); - this.setStatus(TestPlanConstants.TEST_PLAN_STATUS_COMPLETED); - }}); - } this.requestGetWithOk(String.format(URL_TEST_PLAN_ARCHIVED, groupId)); + + //归档不能归档的测试计划 + this.requestGet(String.format(URL_TEST_PLAN_ARCHIVED, rootPlanIds.getFirst())).andExpect(status().is5xxServerError()); + + //归档普通测试计划 + this.requestGetWithOk(String.format(URL_TEST_PLAN_ARCHIVED, testPlanId6)); + // testPlan6更改回去 + TestPlan testPlan = new TestPlan(); + testPlan.setId(testPlanId6); + testPlan.setStatus(TestPlanConstants.TEST_PLAN_STATUS_NOT_ARCHIVED); + testPlanMapper.updateByPrimaryKeySelective(testPlan); } @@ -1003,11 +1083,104 @@ public class TestPlanTests extends BaseTest { TestPlanTableRequest groupRequest = new TestPlanTableRequest(); //查询游离态测试计划 TestPlanTableRequest onlyPlanRequest = new TestPlanTableRequest(); + // 状态过滤的测试计划 + TestPlanTableRequest statusRequest = new TestPlanTableRequest(); BeanUtils.copyBean(groupRequest, dataRequest); BeanUtils.copyBean(onlyPlanRequest, dataRequest); + BeanUtils.copyBean(statusRequest, dataRequest); groupRequest.setType(TestPlanConstants.TEST_PLAN_TYPE_GROUP); onlyPlanRequest.setType(TestPlanConstants.TEST_PLAN_TYPE_PLAN); + + //进行状态筛选 -- 空状态 + testPlanTestService.checkTestPlanPage(this.requestPostWithOkAndReturn( + URL_POST_TEST_PLAN_PAGE, statusRequest).getResponse().getContentAsString(StandardCharsets.UTF_8), + dataRequest.getCurrent(), + dataRequest.getPageSize(), + 999 - 1); + /* + 现有数据状态: + 已完成的 6 47(组) + 进行中的 16 + 已归档的 35(组) + */ + //进行状态筛选 -- 未开始 + statusRequest.setFilter(new HashMap<>() {{ + this.put("status", Collections.singletonList(TestPlanConstants.TEST_PLAN_SHOW_STATUS_PREPARED)); + }}); + testPlanTestService.checkTestPlanPage(this.requestPostWithOkAndReturn( + URL_POST_TEST_PLAN_PAGE, statusRequest).getResponse().getContentAsString(StandardCharsets.UTF_8), + dataRequest.getCurrent(), + dataRequest.getPageSize(), + 999 - 1 - 1 - 2); + //进行状态筛选 -- 已完成 + statusRequest.setFilter(new HashMap<>() {{ + this.put("status", Collections.singletonList(TestPlanConstants.TEST_PLAN_SHOW_STATUS_COMPLETED)); + }}); + testPlanTestService.checkTestPlanPage(this.requestPostWithOkAndReturn( + URL_POST_TEST_PLAN_PAGE, statusRequest).getResponse().getContentAsString(StandardCharsets.UTF_8), + dataRequest.getCurrent(), + dataRequest.getPageSize(), + 2); + //进行状态筛选 -- 进行中 + statusRequest.setFilter(new HashMap<>() {{ + this.put("status", Collections.singletonList(TestPlanConstants.TEST_PLAN_SHOW_STATUS_UNDERWAY)); + }}); + testPlanTestService.checkTestPlanPage(this.requestPostWithOkAndReturn( + URL_POST_TEST_PLAN_PAGE, statusRequest).getResponse().getContentAsString(StandardCharsets.UTF_8), + dataRequest.getCurrent(), + dataRequest.getPageSize(), + 1); + //进行状态筛选 -- 已完成和未开始 + statusRequest.setFilter(new HashMap<>() {{ + this.put("status", new ArrayList() {{ + this.add(TestPlanConstants.TEST_PLAN_SHOW_STATUS_COMPLETED); + this.add(TestPlanConstants.TEST_PLAN_SHOW_STATUS_PREPARED); + }}); + }}); + testPlanTestService.checkTestPlanPage(this.requestPostWithOkAndReturn( + URL_POST_TEST_PLAN_PAGE, statusRequest).getResponse().getContentAsString(StandardCharsets.UTF_8), + dataRequest.getCurrent(), + dataRequest.getPageSize(), + 999 - 1 - 1); + //进行状态筛选 -- 已完成和进行中 + statusRequest.setFilter(new HashMap<>() {{ + this.put("status", new ArrayList() {{ + this.add(TestPlanConstants.TEST_PLAN_SHOW_STATUS_COMPLETED); + this.add(TestPlanConstants.TEST_PLAN_SHOW_STATUS_UNDERWAY); + }}); + }}); + testPlanTestService.checkTestPlanPage(this.requestPostWithOkAndReturn( + URL_POST_TEST_PLAN_PAGE, statusRequest).getResponse().getContentAsString(StandardCharsets.UTF_8), + dataRequest.getCurrent(), + dataRequest.getPageSize(), + 3); + //进行状态筛选 -- 进行中和未开始 + statusRequest.setFilter(new HashMap<>() {{ + this.put("status", new ArrayList() {{ + this.add(TestPlanConstants.TEST_PLAN_SHOW_STATUS_UNDERWAY); + this.add(TestPlanConstants.TEST_PLAN_SHOW_STATUS_PREPARED); + }}); + }}); + testPlanTestService.checkTestPlanPage(this.requestPostWithOkAndReturn( + URL_POST_TEST_PLAN_PAGE, statusRequest).getResponse().getContentAsString(StandardCharsets.UTF_8), + dataRequest.getCurrent(), + dataRequest.getPageSize(), + 999 - 1 - 2); + //进行状态筛选 -- 已完成、未开始、进行中 + statusRequest.setFilter(new HashMap<>() {{ + this.put("status", new ArrayList() {{ + this.add(TestPlanConstants.TEST_PLAN_SHOW_STATUS_UNDERWAY); + this.add(TestPlanConstants.TEST_PLAN_SHOW_STATUS_PREPARED); + this.add(TestPlanConstants.TEST_PLAN_SHOW_STATUS_COMPLETED); + }}); + }}); + testPlanTestService.checkTestPlanPage(this.requestPostWithOkAndReturn( + URL_POST_TEST_PLAN_PAGE, statusRequest).getResponse().getContentAsString(StandardCharsets.UTF_8), + dataRequest.getCurrent(), + dataRequest.getPageSize(), + 999 - 1); + BaseTreeNode a1Node = TestPlanTestUtils.getNodeByName(preliminaryTreeNodes, "a1"); BaseTreeNode a2Node = TestPlanTestUtils.getNodeByName(preliminaryTreeNodes, "a2"); BaseTreeNode a3Node = TestPlanTestUtils.getNodeByName(preliminaryTreeNodes, "a3"); @@ -1024,6 +1197,7 @@ public class TestPlanTests extends BaseTest { dataRequest.getCurrent(), dataRequest.getPageSize(), 999 - 1); + //查询归档的 dataRequest.setFilter(new HashMap<>() {{ this.put("status", Collections.singletonList(TestPlanConstants.TEST_PLAN_STATUS_ARCHIVED)); @@ -1038,13 +1212,13 @@ public class TestPlanTests extends BaseTest { URL_POST_TEST_PLAN_PAGE, groupRequest).getResponse().getContentAsString(StandardCharsets.UTF_8), dataRequest.getCurrent(), dataRequest.getPageSize(), - 3 - 1); + 5 - 1); //只查询计划 testPlanTestService.checkTestPlanPage(this.requestPostWithOkAndReturn( URL_POST_TEST_PLAN_PAGE, onlyPlanRequest).getResponse().getContentAsString(StandardCharsets.UTF_8), dataRequest.getCurrent(), dataRequest.getPageSize(), - 996); + 999 - 5); //按照名称倒叙 dataRequest.setSort(new HashMap<>() {{ @@ -1243,17 +1417,17 @@ public class TestPlanTests extends BaseTest { //修改a2节点下的数据(91,92)的所属测试计划组 updateRequest = testPlanTestService.generateUpdateRequest(testPlanTestService.selectTestPlanByName("testPlan_91").getId()); - updateRequest.setGroupId(groupTestPlanId35); + updateRequest.setGroupId(groupTestPlanId45); this.requestPostWithOk(URL_POST_TEST_PLAN_UPDATE, updateRequest); a2NodeCount--; updateRequest = testPlanTestService.generateUpdateRequest(testPlanTestService.selectTestPlanByName("testPlan_92").getId()); - updateRequest.setGroupId(groupTestPlanId35); + updateRequest.setGroupId(groupTestPlanId45); this.requestPostWithOk(URL_POST_TEST_PLAN_UPDATE, updateRequest); a2NodeCount--; TestPlan updatePlan = new TestPlan(); updatePlan.setId(groupTestPlanId7); - updatePlan.setStatus(TestPlanConstants.TEST_PLAN_STATUS_UNDERWAY); + updatePlan.setStatus(TestPlanConstants.TEST_PLAN_STATUS_NOT_ARCHIVED); testPlanMapper.updateByPrimaryKeySelective(updatePlan); //修改测试计划组信息 updateRequest = testPlanTestService.generateUpdateRequest(groupTestPlanId7); @@ -2113,25 +2287,15 @@ public class TestPlanTests extends BaseTest { public void testArchived() throws Exception { //计划 -- 首先状态不是已完成 this.requestGet(String.format(URL_TEST_PLAN_ARCHIVED, "wx_test_plan_id_1")).andExpect(status().is5xxServerError()); - //更改状态再归档 - TestPlan testPlan = new TestPlan(); - testPlan.setId("wx_test_plan_id_1"); - testPlan.setStatus(TestPlanConstants.TEST_PLAN_STATUS_COMPLETED); - testPlanMapper.updateByPrimaryKeySelective(testPlan); - this.requestGetWithOk(String.format(URL_TEST_PLAN_ARCHIVED, "wx_test_plan_id_1")); - //计划组没有可归档的测试计划: this.requestGet(String.format(URL_TEST_PLAN_ARCHIVED, "wx_test_plan_id_2")).andExpect(status().is5xxServerError()); - this.requestGetWithOk(String.format(URL_TEST_PLAN_ARCHIVED, "wx_test_plan_id_5")); - } @Test @Order(303) public void testCopy() throws Exception { // 1. 已归档的不能再归档计划 无用例 - requestGet(String.format(URL_TEST_PLAN_COPY, "wx_test_plan_id_1")).andExpect(status().is5xxServerError()); - + requestGet(String.format(URL_TEST_PLAN_COPY, groupTestPlanId35)).andExpect(status().is5xxServerError()); // 2.计划 有用例 MvcResult mvcResult1 = this.requestGetWithOkAndReturn(String.format(URL_TEST_PLAN_COPY, "wx_test_plan_id_4"));