From 177d5afda1a23499c3b1e7dc266767ea16b31bbc Mon Sep 17 00:00:00 2001 From: song-cc-rock Date: Sat, 11 May 2024 18:08:35 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=B5=8B=E8=AF=95=E8=AE=A1=E5=88=92):=20?= =?UTF-8?q?=E8=A1=A5=E5=85=85=E8=AE=A1=E5=88=92=E5=88=97=E8=A1=A8=E8=AF=A6?= =?UTF-8?q?=E6=83=85=E9=80=9A=E8=BF=87=E7=8E=87,=20=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E8=BF=9B=E5=BA=A6=E7=BB=9F=E8=AE=A1=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FunctionalCaseExecuteResult.java | 24 +++- .../bug/service/BugCommonService.java | 13 --- .../metersphere/bug/service/BugService.java | 10 +- .../bug/controller/BugControllerTests.java | 6 + .../service/FunctionalCaseService.java | 6 +- .../FunctionalCaseModuleControllerTests.java | 4 +- .../FunctionalTestCaseControllerTests.java | 2 +- .../plan/controller/TestPlanController.java | 10 ++ .../dto/response/TestPlanDetailResponse.java | 9 +- .../plan/dto/response/TestPlanResponse.java | 4 +- .../response/TestPlanStatisticsResponse.java | 51 ++++++++ .../ExtTestPlanFunctionalCaseMapper.java | 8 ++ .../ExtTestPlanFunctionalCaseMapper.xml | 14 +++ .../service/TestPlanBatchArchivedService.java | 2 +- .../plan/service/TestPlanBugService.java | 11 +- .../service/TestPlanManagementService.java | 16 +-- .../plan/service/TestPlanService.java | 8 +- .../service/TestPlanStatisticsService.java | 110 ++++++++++++++++++ .../plan/controller/TestPlanTests.java | 7 ++ .../plan/service/TestPlanTestService.java | 4 +- .../resources/dml/init_test_plan_test.sql | 16 ++- 21 files changed, 278 insertions(+), 57 deletions(-) create mode 100644 backend/services/test-plan/src/main/java/io/metersphere/plan/dto/response/TestPlanStatisticsResponse.java create mode 100644 backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanStatisticsService.java diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/constants/FunctionalCaseExecuteResult.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/constants/FunctionalCaseExecuteResult.java index 0a3a5f107b..a644dd0cfb 100644 --- a/backend/framework/sdk/src/main/java/io/metersphere/sdk/constants/FunctionalCaseExecuteResult.java +++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/constants/FunctionalCaseExecuteResult.java @@ -1,9 +1,25 @@ package io.metersphere.sdk.constants; public enum FunctionalCaseExecuteResult { - UN_EXECUTED, - PASSED, - FAILED, + + /** + * 未执行 + */ + PENDING, + /** + * 通过 + */ + SUCCESS, + /** + * 失败 + */ + ERROR, + /** + * 阻塞 + */ BLOCKED, - SKIPPED + /** + * 误报 + */ + FAKE_ERROR } diff --git a/backend/services/bug-management/src/main/java/io/metersphere/bug/service/BugCommonService.java b/backend/services/bug-management/src/main/java/io/metersphere/bug/service/BugCommonService.java index 83df673d51..0a26703473 100644 --- a/backend/services/bug-management/src/main/java/io/metersphere/bug/service/BugCommonService.java +++ b/backend/services/bug-management/src/main/java/io/metersphere/bug/service/BugCommonService.java @@ -177,19 +177,6 @@ public class BugCommonService { bugFollowerMapper.deleteByExample(followerExample); } - /** - * 获取状态集合 - * @param projectId 项目ID - * @return 处理人集合 - */ - public Map getAllHandlerMap(String projectId) { - // 缺陷表头处理人选项 - List headerOptions = getHeaderHandlerOption(projectId); - List localOptions = getLocalHandlerOption(projectId); - List allHandleOption = ListUtils.union(headerOptions, localOptions).stream().distinct().toList(); - return allHandleOption.stream().collect(Collectors.toMap(SelectOption::getValue, SelectOption::getText)); - } - /** * 获取状态选项 * @param projectId 项目ID diff --git a/backend/services/bug-management/src/main/java/io/metersphere/bug/service/BugService.java b/backend/services/bug-management/src/main/java/io/metersphere/bug/service/BugService.java index eb9c711f80..75b79642d4 100644 --- a/backend/services/bug-management/src/main/java/io/metersphere/bug/service/BugService.java +++ b/backend/services/bug-management/src/main/java/io/metersphere/bug/service/BugService.java @@ -1245,12 +1245,18 @@ public class BugService { List ids = bugs.stream().map(BugDTO::getId).toList(); List customFields = extBugCustomFieldMapper.getBugAllCustomFields(ids, projectId); Map> customFieldMap = customFields.stream().collect(Collectors.groupingBy(BugCustomFieldDTO::getBugId)); - Map allHandlerMap = bugCommonService.getAllHandlerMap(projectId); + // MS处理人会与第三方的值冲突, 分开查询 + List headerOptions = bugCommonService.getHeaderHandlerOption(projectId); + Map headerHandleUserMap = headerOptions.stream().collect(Collectors.toMap(SelectOption::getValue, SelectOption::getText)); + List localOptions = bugCommonService.getLocalHandlerOption(projectId); + Map localHandleUserMap = localOptions.stream().collect(Collectors.toMap(SelectOption::getValue, SelectOption::getText)); + Map allStatusMap = bugCommonService.getAllStatusMap(projectId); bugs.forEach(bug -> { bug.setCustomFields(customFieldMap.get(bug.getId())); // 解析处理人, 状态 - bug.setHandleUserName(allHandlerMap.get(bug.getHandleUser())); + bug.setHandleUserName(headerHandleUserMap.containsKey(bug.getHandleUser()) ? + headerHandleUserMap.get(bug.getHandleUser()) : localHandleUserMap.get(bug.getHandleUser())); bug.setStatusName(allStatusMap.get(bug.getStatus())); }); return bugs; diff --git a/backend/services/bug-management/src/test/java/io/metersphere/bug/controller/BugControllerTests.java b/backend/services/bug-management/src/test/java/io/metersphere/bug/controller/BugControllerTests.java index ef78d198d5..b8da0bc2fc 100644 --- a/backend/services/bug-management/src/test/java/io/metersphere/bug/controller/BugControllerTests.java +++ b/backend/services/bug-management/src/test/java/io/metersphere/bug/controller/BugControllerTests.java @@ -589,6 +589,12 @@ public class BugControllerTests extends BaseTest { MultiValueMap addParam = getMultiPartParam(addRequest, file); this.requestMultipartWithOkAndReturn(BUG_ADD, addParam); + BugPageRequest bugPageRequest = new BugPageRequest(); + bugPageRequest.setCurrent(1); + bugPageRequest.setPageSize(10); + bugPageRequest.setProjectId("default-project-for-bug"); + this.requestPostWithOk(BUG_PAGE, bugPageRequest); + // 更新Jira缺陷 BugEditRequest updateRequest = buildJiraBugRequest(true); updateRequest.setUnLinkRefIds(List.of(getAddJiraAssociateFile().getId())); diff --git a/backend/services/case-management/src/main/java/io/metersphere/functional/service/FunctionalCaseService.java b/backend/services/case-management/src/main/java/io/metersphere/functional/service/FunctionalCaseService.java index 5910320d24..631e698437 100644 --- a/backend/services/case-management/src/main/java/io/metersphere/functional/service/FunctionalCaseService.java +++ b/backend/services/case-management/src/main/java/io/metersphere/functional/service/FunctionalCaseService.java @@ -282,7 +282,7 @@ public class FunctionalCaseService { functionalCase.setReviewStatus(FunctionalCaseReviewStatus.UN_REVIEWED.name()); functionalCase.setPos(getNextOrder(request.getProjectId())); functionalCase.setRefId(caseId); - functionalCase.setLastExecuteResult(FunctionalCaseExecuteResult.UN_EXECUTED.name()); + functionalCase.setLastExecuteResult(FunctionalCaseExecuteResult.PENDING.name()); functionalCase.setLatest(true); functionalCase.setCreateUser(userId); functionalCase.setUpdateUser(userId); @@ -808,7 +808,7 @@ public class FunctionalCaseService { functional.setName(getCopyName(functionalCase.getName(), num, functional.getNum())); functional.setReviewStatus(FunctionalCaseReviewStatus.UN_REVIEWED.name()); functional.setPos(nextOrder.get()); - functional.setLastExecuteResult(FunctionalCaseExecuteResult.UN_EXECUTED.name()); + functional.setLastExecuteResult(FunctionalCaseExecuteResult.PENDING.name()); functional.setCreateUser(userId); functional.setUpdateUser(userId); functional.setCreateTime(System.currentTimeMillis()); @@ -1125,7 +1125,7 @@ public class FunctionalCaseService { functionalCase.setPos(nextOrder); functionalCase.setVersionId(request.getVersionId()); functionalCase.setRefId(caseId); - functionalCase.setLastExecuteResult(FunctionalCaseExecuteResult.UN_EXECUTED.name()); + functionalCase.setLastExecuteResult(FunctionalCaseExecuteResult.PENDING.name()); functionalCase.setLatest(true); functionalCase.setCreateUser(userId); functionalCase.setUpdateUser(userId); diff --git a/backend/services/case-management/src/test/java/io/metersphere/functional/controller/FunctionalCaseModuleControllerTests.java b/backend/services/case-management/src/test/java/io/metersphere/functional/controller/FunctionalCaseModuleControllerTests.java index dd7326c91f..25bc65c17d 100644 --- a/backend/services/case-management/src/test/java/io/metersphere/functional/controller/FunctionalCaseModuleControllerTests.java +++ b/backend/services/case-management/src/test/java/io/metersphere/functional/controller/FunctionalCaseModuleControllerTests.java @@ -23,13 +23,13 @@ import io.metersphere.system.log.constants.OperationLogType; import io.metersphere.system.uid.IDGenerator; import jakarta.annotation.Resource; import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.*; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -import org.apache.commons.lang3.StringUtils; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -763,7 +763,7 @@ public class FunctionalCaseModuleControllerTests extends BaseTest { functionalCase.setPos(500L); functionalCase.setVersionId("12335"); functionalCase.setRefId(functionalCase.getId()); - functionalCase.setLastExecuteResult(FunctionalCaseExecuteResult.UN_EXECUTED.name()); + functionalCase.setLastExecuteResult(FunctionalCaseExecuteResult.PENDING.name()); functionalCase.setPublicCase(false); functionalCase.setLatest(true); functionalCase.setCreateUser("gyq"); diff --git a/backend/services/case-management/src/test/java/io/metersphere/functional/controller/FunctionalTestCaseControllerTests.java b/backend/services/case-management/src/test/java/io/metersphere/functional/controller/FunctionalTestCaseControllerTests.java index ad3ac40eef..1785d34960 100644 --- a/backend/services/case-management/src/test/java/io/metersphere/functional/controller/FunctionalTestCaseControllerTests.java +++ b/backend/services/case-management/src/test/java/io/metersphere/functional/controller/FunctionalTestCaseControllerTests.java @@ -450,7 +450,7 @@ public class FunctionalTestCaseControllerTests extends BaseTest { functionalCase.setPos(500L); functionalCase.setVersionId("12335"); functionalCase.setRefId(functionalCase.getId()); - functionalCase.setLastExecuteResult(FunctionalCaseExecuteResult.UN_EXECUTED.name()); + functionalCase.setLastExecuteResult(FunctionalCaseExecuteResult.PENDING.name()); functionalCase.setPublicCase(false); functionalCase.setLatest(true); functionalCase.setCreateUser("gyq"); diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/controller/TestPlanController.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/controller/TestPlanController.java index f21156db37..8a1d782580 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/controller/TestPlanController.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/controller/TestPlanController.java @@ -5,9 +5,11 @@ import io.metersphere.plan.domain.TestPlan; import io.metersphere.plan.dto.request.*; import io.metersphere.plan.dto.response.TestPlanDetailResponse; import io.metersphere.plan.dto.response.TestPlanResponse; +import io.metersphere.plan.dto.response.TestPlanStatisticsResponse; import io.metersphere.plan.service.TestPlanLogService; import io.metersphere.plan.service.TestPlanManagementService; import io.metersphere.plan.service.TestPlanService; +import io.metersphere.plan.service.TestPlanStatisticsService; import io.metersphere.sdk.constants.HttpMethodConstants; import io.metersphere.sdk.constants.PermissionConstants; import io.metersphere.system.log.annotation.Log; @@ -35,6 +37,8 @@ public class TestPlanController { private TestPlanService testPlanService; @Resource private TestPlanManagementService testPlanManagementService; + @Resource + private TestPlanStatisticsService testPlanStatisticsService; @PostMapping("/page") @@ -46,6 +50,12 @@ public class TestPlanController { return testPlanManagementService.page(request); } + @PostMapping("/statistics") + @Operation(summary = "测试计划-获取计划详情统计{通过率, 执行进度}") + @RequiresPermissions(PermissionConstants.TEST_PLAN_READ) + public List selectTestPlanMetricById(@RequestBody List ids) { + return testPlanStatisticsService.calculateRate(ids); + } @PostMapping("/module/count") @Operation(summary = "测试计划-模块统计") diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/response/TestPlanDetailResponse.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/response/TestPlanDetailResponse.java index 577e4e3e12..482649390c 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/response/TestPlanDetailResponse.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/response/TestPlanDetailResponse.java @@ -11,13 +11,11 @@ import java.util.List; * @author wx */ @Data -public class TestPlanDetailResponse implements Serializable { +public class TestPlanDetailResponse extends TestPlanStatisticsResponse implements Serializable { + @Serial private static final long serialVersionUID = 1L; - @Schema(description = "测试计划ID") - private String id; - @Schema(description = "测试计划组Id") private String groupId; @Schema(description = "测试计划组名称") @@ -38,9 +36,6 @@ public class TestPlanDetailResponse implements Serializable { @Schema(description = "是否允许重复添加用例") private Boolean repeatCase; - @Schema(description = "测试计划通过阈值;0-100") - private Double passThreshold; - @Schema(description = "是否开启测试规划") private Boolean testPlanning; 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 8fbf06f49c..c02aeb26ed 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 @@ -6,9 +6,7 @@ import lombok.Data; import java.util.List; @Data -public class TestPlanResponse { - @Schema(description = "测试计划ID") - private String id; +public class TestPlanResponse extends TestPlanStatisticsResponse { @Schema(description = "项目ID") private String projectId; @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 new file mode 100644 index 0000000000..36966172ed --- /dev/null +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/dto/response/TestPlanStatisticsResponse.java @@ -0,0 +1,51 @@ +package io.metersphere.plan.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 测试计划统计详情 + */ +@Data +@EqualsAndHashCode(callSuper = false) +public class TestPlanStatisticsResponse { + + @Schema(description = "测试计划ID") + private String id; + + @Schema(description = "测试计划通过阈值{0-100}") + private Double passThreshold; + + @Schema(description = "测试计划: 通过率 {成功用例/全部用例}") + private Double passRate; + + @Schema(description = "测试计划: 执行进度||测试进度 {已执行用例/全部用例}") + private Double executeRate; + + /** + * 执行进度中的用例数量统计 (暂定) + */ + @Schema(description = "成功用例数量") + private Integer successCount = 0; + @Schema(description = "失败用例数量") + private Integer errorCount = 0; + @Schema(description = "误报用例数量") + private Integer fakeErrorCount = 0; + @Schema(description = "阻塞用例数量") + private Integer blockCount = 0; + @Schema(description = "未执行用例数量") + private Integer pendingCount = 0; + + /** + * 用例数中用例数量统计 (暂定) + */ + @Schema(description = "用例总数") + private Integer caseTotal = 0; + @Schema(description = "功能用例数量") + private Integer functionalCaseCount = 0; + @Schema(description = "接口用例数量") + private Integer apiCaseCount = 0; + @Schema(description = "接口场景数量") + private Integer apiScenarioCount = 0; +} 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 7c68db919a..6f692c7824 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 @@ -3,6 +3,7 @@ package io.metersphere.plan.mapper; import io.metersphere.functional.dto.FunctionalCaseModuleCountDTO; import io.metersphere.functional.dto.FunctionalCaseModuleDTO; import io.metersphere.functional.dto.ProjectOptionDTO; +import io.metersphere.plan.domain.TestPlanFunctionalCase; import io.metersphere.plan.dto.AssociationNode; import io.metersphere.plan.dto.ResourceSelectParam; import io.metersphere.plan.dto.request.BasePlanCaseBatchRequest; @@ -38,4 +39,11 @@ public interface ExtTestPlanFunctionalCaseMapper { long caseCount(@Param("request") TestPlanCaseRequest request, @Param("deleted") boolean deleted); List getIds(@Param("request") BasePlanCaseBatchRequest request, @Param("deleted") boolean deleted); + + /** + * 获取计划下的功能用例集合 + * @param planIds 测试计划ID集合 + * @return 计划功能用例集合 + */ + List getPlanFunctionalCaseByIds(@Param("planIds") List planIds); } 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 c34b0ec998..8f64e1340a 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 @@ -465,4 +465,18 @@ AND functional_case.deleted = #{deleted} + + \ No newline at end of file diff --git a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanBatchArchivedService.java b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanBatchArchivedService.java index 9d02ff6f47..a99619cef1 100644 --- a/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanBatchArchivedService.java +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanBatchArchivedService.java @@ -37,7 +37,7 @@ public class TestPlanBatchArchivedService extends TestPlanBaseUtilsService { * @param plans */ private int batchArchivedGroup(Map> plans, TestPlanBatchProcessRequest request, String userId) { - //TODO 批量移动计划组 + //TODO 批量归档计划组 return 0; } 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 f272d65580..96b54a94c2 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 @@ -8,6 +8,7 @@ import io.metersphere.plan.dto.TestPlanBugCaseDTO; import io.metersphere.plan.dto.request.TestPlanBugPageRequest; import io.metersphere.plan.dto.response.TestPlanBugPageResponse; import io.metersphere.plan.mapper.ExtTestPlanBugMapper; +import io.metersphere.plugin.platform.dto.SelectOption; import io.metersphere.system.dto.sdk.OptionDTO; import io.metersphere.system.mapper.BaseUserMapper; import jakarta.annotation.Resource; @@ -86,11 +87,17 @@ public class TestPlanBugService extends TestPlanResourceService { * @param bugList 缺陷集合 */ private void parseCustomField(List bugList, String projectId) { - Map allHandlerMap = bugCommonService.getAllHandlerMap(projectId); + // MS处理人会与第三方的值冲突, 分开查询 + List headerOptions = bugCommonService.getHeaderHandlerOption(projectId); + Map headerHandleUserMap = headerOptions.stream().collect(Collectors.toMap(SelectOption::getValue, SelectOption::getText)); + List localOptions = bugCommonService.getLocalHandlerOption(projectId); + Map localHandleUserMap = localOptions.stream().collect(Collectors.toMap(SelectOption::getValue, SelectOption::getText)); + Map allStatusMap = bugCommonService.getAllStatusMap(projectId); bugList.forEach(bug -> { // 解析处理人, 状态 - bug.setHandleUser(allHandlerMap.get(bug.getHandleUser())); + bug.setHandleUser(headerHandleUserMap.containsKey(bug.getHandleUser()) ? + headerHandleUserMap.get(bug.getHandleUser()) : localHandleUserMap.get(bug.getHandleUser())); bug.setStatus(allStatusMap.get(bug.getStatus())); }); } 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 5ebd91111b..9b34f9f568 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 @@ -22,7 +22,6 @@ import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -38,6 +37,8 @@ public class TestPlanManagementService { private ExtTestPlanModuleMapper extTestPlanModuleMapper; @Resource private TestPlanModuleService testPlanModuleService; + @Resource + private TestPlanStatisticsService testPlanStatisticsService; public Map moduleCount(TestPlanTableRequest request) { //查出每个模块节点下的资源数量。 不需要按照模块进行筛选 @@ -80,23 +81,14 @@ public class TestPlanManagementService { if (collect.containsKey(item.getId())) { //存在子节点 List list = collect.get(item.getId()); - calculateData(list); + testPlanStatisticsService.calculateCaseCount(list); item.setChildren(list); item.setChildrenCount(list.size()); } - calculateData(Arrays.asList(item)); + testPlanStatisticsService.calculateCaseCount(List.of(item)); }); } - /** - * 计算各种指标 - * - * @param list - */ - private void calculateData(List list) { - //TODO 查询计划下面关联的用例数量,用于各种计算 什么通过率 进度 用例数 - } - public void checkModuleIsOpen(String resourceId, String resourceType, List moduleMenus) { Project project; 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 a1213bb4d9..5ff1394ce1 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 @@ -52,6 +52,8 @@ public class TestPlanService extends TestPlanBaseUtilsService { @Resource private TestPlanBatchArchivedService testPlanBatchArchivedService; @Resource + private TestPlanStatisticsService testPlanStatisticsService; + @Resource private TestPlanCaseService testPlanCaseService; @@ -151,7 +153,6 @@ public class TestPlanService extends TestPlanBaseUtilsService { /** * 计划组删除的相关逻辑(待定) - * * @param testPlanGroupIds 计划组ID集合 */ private void deleteGroupByList(List testPlanGroupIds) { @@ -423,8 +424,8 @@ public class TestPlanService extends TestPlanBaseUtilsService { /** * 获取单个测试计划或测试计划组详情(用于编辑) * - * @param id - * @return + * @param id 计划ID + * @return 计划的详情数据 */ public TestPlanDetailResponse detail(String id) { TestPlan testPlan = testPlanMapper.selectByPrimaryKey(id); @@ -443,6 +444,7 @@ public class TestPlanService extends TestPlanBaseUtilsService { response.setPlannedStartTime(testPlan.getPlannedStartTime()); response.setPlannedEndTime(testPlan.getPlannedEndTime()); getOtherConfig(response, testPlan); + testPlanStatisticsService.calculateCaseCount(List.of(response)); } return response; } 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 new file mode 100644 index 0000000000..7810bbf94e --- /dev/null +++ b/backend/services/test-plan/src/main/java/io/metersphere/plan/service/TestPlanStatisticsService.java @@ -0,0 +1,110 @@ +package io.metersphere.plan.service; + +import io.metersphere.plan.domain.TestPlanFunctionalCase; +import io.metersphere.plan.dto.response.TestPlanStatisticsResponse; +import io.metersphere.plan.mapper.ExtTestPlanFunctionalCaseMapper; +import io.metersphere.sdk.constants.FunctionalCaseExecuteResult; +import jakarta.annotation.Resource; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.stereotype.Service; + +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 测试计划统计相关业务 + */ +@Service +public class TestPlanStatisticsService { + + @Resource + private ExtTestPlanFunctionalCaseMapper extTestPlanFunctionalCaseMapper; + + /** + * 计划的用例统计数据 + * + * @param plans 计划集合 + */ + public void calculateCaseCount(List plans) { + // TODO 计算计划下各种维度的用例统计数目 (待定) + /* + * 1. 查询计划下的用例数据集合(目前只有功能用例) + * 2. 根据执行结果统计(结果小数保留两位) + */ + + List planIds = plans.stream().map(TestPlanStatisticsResponse::getId).toList(); + // 计划-功能用例的关联数据 + List planFunctionalCases = extTestPlanFunctionalCaseMapper.getPlanFunctionalCaseByIds(planIds); + Map> planFunctionalCaseMap = planFunctionalCases.stream().collect(Collectors.groupingBy(TestPlanFunctionalCase::getTestPlanId)); + // TODO: 计划-接口用例的关联数据 + plans.forEach(plan -> { + // 功能用例统计开始 + List functionalCases = planFunctionalCaseMap.get(plan.getId()); + plan.setFunctionalCaseCount(CollectionUtils.isNotEmpty(functionalCases) ? functionalCases.size() : 0); + // TODO: 接口用例统计开始 + + // FIXME: CaseTotal后续会补充接口用例及场景的统计数据 + plan.setCaseTotal(plan.getFunctionalCaseCount()); + }); + } + + /** + * 计划的{通过率, 执行进度}统计数据 + * + * @param planIds 计划ID集合 + */ + public List calculateRate(List planIds) { + List planStatisticsResponses = new ArrayList<>(); + // TODO 计算计划下的用例通过率, 执行进度 (待定) + /* + * 1. 查询计划下的用例数据集合(目前只有功能用例) + * 2. 根据执行结果统计(结果小数保留两位) + */ + DecimalFormat rateFormat = new DecimalFormat("#0.00"); + rateFormat.setMinimumFractionDigits(2); + rateFormat.setMaximumFractionDigits(2); + + // 计划-功能用例的关联数据 + List planFunctionalCases = extTestPlanFunctionalCaseMapper.getPlanFunctionalCaseByIds(planIds); + Map> planFunctionalCaseMap = planFunctionalCases.stream().collect(Collectors.groupingBy(TestPlanFunctionalCase::getTestPlanId)); + // TODO: 计划-接口用例的关联数据 + planIds.forEach(planId -> { + TestPlanStatisticsResponse statisticsResponse = new TestPlanStatisticsResponse(); + int success = 0, error = 0, fakeError = 0, block = 0, pending = 0; + // 功能用例统计开始 + List functionalCases = planFunctionalCaseMap.get(planId); + statisticsResponse.setFunctionalCaseCount(CollectionUtils.isNotEmpty(functionalCases) ? functionalCases.size() : 0); + Map> functionalCaseResultMap = CollectionUtils.isEmpty(functionalCases) ? new HashMap<>(16) : functionalCases.stream().collect(Collectors.groupingBy(TestPlanFunctionalCase::getLastExecResult)); + success += functionalCaseResultMap.containsKey(FunctionalCaseExecuteResult.SUCCESS.name()) ? functionalCaseResultMap.get(FunctionalCaseExecuteResult.SUCCESS.name()).size() : 0; + error += functionalCaseResultMap.containsKey(FunctionalCaseExecuteResult.ERROR.name()) ? functionalCaseResultMap.get(FunctionalCaseExecuteResult.ERROR.name()).size() : 0; + fakeError += functionalCaseResultMap.containsKey(FunctionalCaseExecuteResult.FAKE_ERROR.name()) ? functionalCaseResultMap.get(FunctionalCaseExecuteResult.FAKE_ERROR.name()).size() : 0; + block += functionalCaseResultMap.containsKey(FunctionalCaseExecuteResult.BLOCKED.name()) ? functionalCaseResultMap.get(FunctionalCaseExecuteResult.BLOCKED.name()).size() : 0; + pending += functionalCaseResultMap.containsKey(FunctionalCaseExecuteResult.PENDING.name()) ? functionalCaseResultMap.get(FunctionalCaseExecuteResult.PENDING.name()).size() : 0; + // TODO: 接口用例统计开始 + + + // 用例数据汇总 + statisticsResponse.setSuccessCount(success); + statisticsResponse.setErrorCount(error); + statisticsResponse.setFakeErrorCount(fakeError); + statisticsResponse.setBlockCount(block); + statisticsResponse.setPendingCount(pending); + // FIXME: CaseTotal后续会补充接口用例及场景的统计数据 + statisticsResponse.setCaseTotal(statisticsResponse.getFunctionalCaseCount()); + // 通过率 {通过用例数/总用例数} && 执行进度 {非未执行的用例数/总用例数} + double passRate = (statisticsResponse.getSuccessCount() == 0 || statisticsResponse.getCaseTotal() == 0) ? 0.00 : + Double.parseDouble(rateFormat.format((double) statisticsResponse.getSuccessCount() * 100 / (double) statisticsResponse.getCaseTotal())); + double executeRate = (statisticsResponse.getPendingCount().equals(statisticsResponse.getCaseTotal()) || statisticsResponse.getCaseTotal() == 0) ? 0.00 : + Double.parseDouble(rateFormat.format((double) (statisticsResponse.getCaseTotal() - statisticsResponse.getPendingCount()) * 100 / (double) statisticsResponse.getCaseTotal())); + // V2旧逻辑, 如果算出的结果(99.999%)由于精度问题四舍五入为100%, 且计算数量小于总数, 实际值设为99.99% + statisticsResponse.setPassRate((passRate == 100 && statisticsResponse.getSuccessCount() < statisticsResponse.getCaseTotal()) ? 99.99 : passRate); + statisticsResponse.setExecuteRate((executeRate == 100 && statisticsResponse.getPendingCount() > 0) ? 99.99 : executeRate); + planStatisticsResponses.add(statisticsResponse); + }); + return planStatisticsResponses; + } +} 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 5f9b853b92..14c25ec67a 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 @@ -101,6 +101,7 @@ public class TestPlanTests extends BaseTest { private static final String URL_GET_TEST_PLAN_DELETE = "/test-plan/delete/%s"; private static final String URL_POST_TEST_PLAN_PAGE = "/test-plan/page"; + private static final String URL_POST_TEST_PLAN_STATISTICS = "/test-plan/statistics"; private static final String URL_POST_TEST_PLAN_MODULE_COUNT = "/test-plan/module/count"; private static final String URL_POST_TEST_PLAN_ADD = "/test-plan/add"; private static final String URL_POST_TEST_PLAN_UPDATE = "/test-plan/update"; @@ -1842,4 +1843,10 @@ public class TestPlanTests extends BaseTest { this.requestPostWithOkAndReturn(URL_POST_RESOURCE_CASE_ASSOCIATION, request); } + @Test + @Order(306) + public void testStatistics() throws Exception { + this.requestPostWithOk(URL_POST_TEST_PLAN_STATISTICS, List.of("wx_test_plan_id_7")); + } + } diff --git a/backend/services/test-plan/src/test/java/io/metersphere/plan/service/TestPlanTestService.java b/backend/services/test-plan/src/test/java/io/metersphere/plan/service/TestPlanTestService.java index 889b7dd750..9f4554d053 100644 --- a/backend/services/test-plan/src/test/java/io/metersphere/plan/service/TestPlanTestService.java +++ b/backend/services/test-plan/src/test/java/io/metersphere/plan/service/TestPlanTestService.java @@ -19,9 +19,9 @@ import io.metersphere.system.uid.IDGenerator; import io.metersphere.system.uid.NumGenerator; import jakarta.annotation.Resource; import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.Assertions; import org.springframework.stereotype.Service; -import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; import java.util.List; @@ -138,7 +138,7 @@ public class TestPlanTestService { functionalCase.setReviewStatus("UN_REVIEWED"); functionalCase.setPos((long) (i * 64)); functionalCase.setRefId(functionalCase.getId()); - functionalCase.setLastExecuteResult(FunctionalCaseExecuteResult.UN_EXECUTED.name()); + functionalCase.setLastExecuteResult(FunctionalCaseExecuteResult.PENDING.name()); functionalCase.setLatest(true); functionalCase.setCreateUser("admin"); functionalCase.setCreateTime(System.currentTimeMillis()); diff --git a/backend/services/test-plan/src/test/resources/dml/init_test_plan_test.sql b/backend/services/test-plan/src/test/resources/dml/init_test_plan_test.sql index 3f4362c664..aa2ffb835a 100644 --- a/backend/services/test-plan/src/test/resources/dml/init_test_plan_test.sql +++ b/backend/services/test-plan/src/test/resources/dml/init_test_plan_test.sql @@ -9,8 +9,20 @@ VALUES ('wx_test_plan_id_7', 30000, '123', 'NONE', '1', '测试组4下计划', 'COMPLETED', 'TEST_PLAN', NULL, 1714980158000, 'WX', 1714980158000, 'WX', 1714980158000, 1714980158000, 1714980158000, 1714980158000, '11'); -INSERT INTO `test_plan_functional_case`(`id`, `test_plan_id`, `functional_case_id`, `create_time`, `create_user`, `execute_user`, `last_exec_time`, `last_exec_result`, `pos`) -VALUES ('wx_tpfc_1', 'wx_test_plan_id_4', 'wx_fc_1', 1714980158000, 'admin', NULL, NULL, NULL, 1); +INSERT INTO `test_plan_functional_case`(`id`, `test_plan_id`, `functional_case_id`, `create_time`, `create_user`, `execute_user`, `last_exec_time`, `last_exec_result`, `pos`) VALUES + ('wx_tpfc_1', 'wx_test_plan_id_4', 'wx_fc_1', 1714980158000, 'admin', NULL, NULL, NULL, 1), + ('oasis_1', 'wx_test_plan_id_7', 'oasis_fc_1', 1714980158000, 'admin', NULL, NULL, 'UN_EXECUTED', 1), + ('oasis_2', 'wx_test_plan_id_7', 'oasis_fc_2', 1714980158000, 'admin', NULL, NULL, 'PASSED', 1), + ('oasis_3', 'wx_test_plan_id_7', 'oasis_fc_3', 1714980158000, 'admin', NULL, NULL, 'FAILED', 1), + ('oasis_4', 'wx_test_plan_id_7', 'oasis_fc_4', 1714980158000, 'admin', NULL, NULL, 'BLOCKED', 1), + ('oasis_5', 'wx_test_plan_id_7', 'oasis_fc_5', 1714980158000, 'admin', NULL, NULL, 'FAKE_ERROR', 1); + +INSERT INTO functional_case(id, num, module_id, project_id, template_id, name, review_status, tags, case_edit_type, pos, version_id, ref_id, last_execute_result, deleted, public_case, latest, create_user, update_user, delete_user, create_time, update_time, delete_time) VALUES + ('oasis_fc_1', 10001, 'TEST_MODULE_ID', 'project-associate-case-test', '100001', '测试', 'UN_REVIEWED', NULL, 'STEP', 0, 'v1.0.0', 'v1.0.0', 'PENDING', b'0', b'0', b'1', 'admin', 'admin', '', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL), + ('oasis_fc_2', 10002, 'TEST_MODULE_ID', 'project-associate-case-test', '100001', '测试', 'UN_REVIEWED', NULL, 'STEP', 0, 'v1.0.0', 'v1.0.0', 'SUCCESS', b'0', b'0', b'1', 'admin', 'admin', '', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL), + ('oasis_fc_3', 10003, 'TEST_MODULE_ID', 'project-associate-case-test', '100001', '测试', 'UN_REVIEWED', NULL, 'STEP', 0, 'v1.0.0', 'v1.0.0', 'ERROR', b'0', b'0', b'1', 'admin', 'admin', '', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL), + ('oasis_fc_4', 10004, 'TEST_MODULE_ID', 'project-associate-case-test', '100001', '测试', 'UN_REVIEWED', NULL, 'STEP', 0, 'v1.0.0', 'v1.0.0', 'BLOCKED', b'0', b'0', b'1', 'admin', 'admin', '', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL), + ('oasis_fc_5', 10005, 'TEST_MODULE_ID', 'project-associate-case-test', '100001', '测试', 'UN_REVIEWED', NULL, 'STEP', 0, 'v1.0.0', 'v1.0.0', 'FAKE_ERROR', b'0', b'0', b'1', 'admin', 'admin', '', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, NULL); INSERT INTO `test_plan_module`(`id`, `project_id`, `name`, `parent_id`, `pos`, `create_time`, `update_time`, `create_user`, `update_user`)