From 6c509781e95343173796368320a3b5c67cf8d42d Mon Sep 17 00:00:00 2001 From: lzh Date: Tue, 20 Jan 2026 18:08:37 +0800 Subject: [PATCH 1/2] docs: update testing guide with unit test details --- docs/ops-architecture/part8-测试指南.md | 92 +++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 docs/ops-architecture/part8-测试指南.md diff --git a/docs/ops-architecture/part8-测试指南.md b/docs/ops-architecture/part8-测试指南.md new file mode 100644 index 0000000..7a5e6cd --- /dev/null +++ b/docs/ops-architecture/part8-测试指南.md @@ -0,0 +1,92 @@ +# Ops 模块测试指南 + +本文档介绍了 Ops 模块(运维工单系统)的测试策略、核心测试类及执行方法。我们的测试重点在于验证业务逻辑的正确性和状态机转换的安全性。 + +## 1. 测试策略概述 + +Ops 模块的测试策略遵循 "金字塔模型",侧重于 Service 层逻辑和状态机(FSM)的单元测试。 + +* **核心关注点**: + * **状态机逻辑**:验证工单状态转换的合法性、非法转换拦截及幂等性。 + * **业务流程**:验证工单从创建、分配、接单到完成的标准流转(Happy Path)。 + * **边界情况**:验证特殊流程(如从排队中分配)及异常处理。 +* **Mock 策略**:使用 Mockito 模拟数据库操作(Mapper)和外部依赖(如 Environment 模块、消息队列),确保测试聚焦于 Ops 模块自身的业务逻辑。 + +## 2. 核心测试类 + +我们主要通过以下两个测试类来保障核心功能的稳定性。 + +### 2.1 OrderStateMachineTest +位于:`viewsh-module-ops-biz/src/test/java/com/viewsh/module/ops/service/fsm/OrderStateMachineTest.java` + +* **职责**:专门测试状态机 `OrderStateMachine` 的行为。 +* **覆盖场景**: + * **合法转换**:如 `PENDING` -> `DISPATCHED`,验证状态更新事件是否正确发布。 + * **非法转换**:如 `PENDING` -> `COMPLETED`,验证是否抛出 `IllegalStateException`。 + * **幂等性**:验证状态不变时(如 `PENDING` -> `PENDING`)是否跳过无用操作。 + * **状态查询**:验证 `getAllowedTransitions` 返回正确的允许目标状态列表。 + +### 2.2 OpsOrderServiceTest +位于:`viewsh-module-ops-biz/src/test/java/com/viewsh/module/ops/service/order/OpsOrderServiceTest.java` + +* **职责**:测试工单服务 `OpsOrderService` 的业务方法。 +* **覆盖场景**: + * **CRUD**:验证 `createOrder`(含默认值填充)、`updateOrder`、`deleteOrder` 等基础操作。 + * **Happy Path**:模拟标准业务流:`assignOrder` (分配) -> `acceptOrder` (接单) -> `completeOrder` (完成)。 + * **Cleaning Flow**:验证保洁业务特有的流程,例如从 `QUEUED`(排队中)状态进行分配。 + * **生命周期委托**:验证 `pause`、`resume`、`cancel` 操作是否正确委托给 `OrderLifecycleManager`,确保与队列状态同步。 + +## 3. 如何执行测试 + +我们使用 Maven 来运行测试。 + +### 运行所有 Ops 模块测试 + +在项目根目录下执行: + +```bash +mvn test -pl viewsh-module-ops/viewsh-module-ops-biz +``` + +### 运行特定测试类 + +如果只想运行状态机的测试: + +```bash +mvn test -Dtest=OrderStateMachineTest -pl viewsh-module-ops/viewsh-module-ops-biz +``` + +如果只想运行工单服务的测试: + +```bash +mvn test -Dtest=OpsOrderServiceTest -pl viewsh-module-ops/viewsh-module-ops-biz +``` + +## 4. 测试覆盖范围说明 + +* **已覆盖**: + * `OrderStateMachine` 的所有核心转换规则。 + * `OpsOrderServiceImpl` 的主要业务方法。 + * 工单创建时的默认参数逻辑。 + * 权限校验逻辑(如非执行人操作拦截)。 +* **Mock / 未覆盖**: + * **数据库交互**:Mapper 层被 Mock,不连接真实数据库。 + * **外部集成**:Environment 模块的交互、IoT 设备消息、Redis 队列操作均通过 Mock 处理。 + * **Controller 层**:目前仅测试 Service 层,API 接口测试不在本指南范围内。 + +## 5. 既有规范回顾 + +以下内容继承自原[Part 8: 测试指南]: + +### 5.1 依赖模拟 (Mocking) +使用 `JUnit 5` 和 `Mockito`。 +- **核心原则**:Mock 掉 Mapper 层和外部 RPC 调用,专注验证 Service 层的状态转换逻辑。 + +### 5.2 集成测试规范(未来计划) +由于采用 Redis + MySQL 双写架构,未来的集成测试需重点验证: +- 事务提交后,Redis 队列中的分数(Score)与优先级(Priority)是否匹配。 +- `QueueSyncHandler` 是否正确处理了缓存失效后的补偿逻辑。 + +### 5.3 常用测试工具 +- **Podam**:用于快速生成填充了随机数据的 DO/DTO 对象。 +- **BaseDbAndRedisUnitTest**:支持 H2 内存数据库和内置 Redis 的集成测试基类。 From 8c1133bb8c7ff1c219e461e35ccd86d26636cc8c Mon Sep 17 00:00:00 2001 From: lzh Date: Tue, 20 Jan 2026 18:10:59 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E7=94=A8=E4=BE=8B=E5=A4=B1=E8=B4=A5=E5=B9=B6=E5=AE=8C=E5=96=84?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E8=A6=86=E7=9B=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/fsm/OrderStateMachineTest.java | 7 +++- .../service/order/OpsOrderServiceTest.java | 38 ++++++++++++++++--- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/viewsh-module-ops/viewsh-module-ops-biz/src/test/java/com/viewsh/module/ops/service/fsm/OrderStateMachineTest.java b/viewsh-module-ops/viewsh-module-ops-biz/src/test/java/com/viewsh/module/ops/service/fsm/OrderStateMachineTest.java index 030531f..2667e76 100644 --- a/viewsh-module-ops/viewsh-module-ops-biz/src/test/java/com/viewsh/module/ops/service/fsm/OrderStateMachineTest.java +++ b/viewsh-module-ops/viewsh-module-ops-biz/src/test/java/com/viewsh/module/ops/service/fsm/OrderStateMachineTest.java @@ -1,5 +1,6 @@ package com.viewsh.module.ops.service.fsm; +import com.viewsh.module.ops.core.event.OrderEventPublisher; import com.viewsh.module.ops.dal.dataobject.workorder.OpsOrderDO; import com.viewsh.module.ops.dal.mysql.workorder.OpsOrderMapper; import com.viewsh.module.ops.enums.OperatorTypeEnum; @@ -34,6 +35,9 @@ public class OrderStateMachineTest { @Mock private OpsOrderEventService eventService; + @Mock + private OrderEventPublisher eventPublisher; + private OpsOrderDO testOrder; @BeforeEach @@ -227,9 +231,10 @@ public class OrderStateMachineTest { // Then assertNotNull(allowedTransitions); - assertEquals(2, allowedTransitions.size()); + assertEquals(3, allowedTransitions.size()); assertTrue(allowedTransitions.contains(WorkOrderStatusEnum.DISPATCHED)); assertTrue(allowedTransitions.contains(WorkOrderStatusEnum.CANCELLED)); + assertTrue(allowedTransitions.contains(WorkOrderStatusEnum.QUEUED)); } @Test diff --git a/viewsh-module-ops/viewsh-module-ops-biz/src/test/java/com/viewsh/module/ops/service/order/OpsOrderServiceTest.java b/viewsh-module-ops/viewsh-module-ops-biz/src/test/java/com/viewsh/module/ops/service/order/OpsOrderServiceTest.java index e84e8ad..c95125f 100644 --- a/viewsh-module-ops/viewsh-module-ops-biz/src/test/java/com/viewsh/module/ops/service/order/OpsOrderServiceTest.java +++ b/viewsh-module-ops/viewsh-module-ops-biz/src/test/java/com/viewsh/module/ops/service/order/OpsOrderServiceTest.java @@ -105,12 +105,13 @@ public class OpsOrderServiceTest { // Then assertNotNull(orderId); verify(opsOrderMapper, times(1)).insert((OpsOrderDO) any()); - verify(orderStateMachine, times(1)).transition( + // createOrder does not use state machine transition, it sets status directly + verify(orderStateMachine, never()).transition( any(OpsOrderDO.class), - eq(WorkOrderStatusEnum.PENDING), - eq(OperatorTypeEnum.SYSTEM), - eq(null), - eq("创建工单") + any(WorkOrderStatusEnum.class), + any(OperatorTypeEnum.class), + any(), + anyString() ); } @@ -227,7 +228,7 @@ public class OpsOrderServiceTest { // Then verify(orderStateMachine, times(1)).transition( eq(testOrder), - eq(WorkOrderStatusEnum.PENDING), + eq(WorkOrderStatusEnum.DISPATCHED), eq(OperatorTypeEnum.ADMIN), eq(1002L), eq("张师傅负责该区域") @@ -375,4 +376,29 @@ public class OpsOrderServiceTest { verify(orderLifecycleManager, never()).cancelOrder(anyLong(), anyLong(), any(OperatorTypeEnum.class), anyString()); } + @Test + void testAssignOrder_FromQueuedStatus_Success() { + // Given + testOrder.setStatus(WorkOrderStatusEnum.QUEUED.getStatus()); + OpsOrderAssignReqDTO assignReq = new OpsOrderAssignReqDTO(); + assignReq.setOrderId(1L); + assignReq.setAssigneeId(2001L); + assignReq.setRemark("从队列分配"); + + when(opsOrderMapper.selectById(1L)).thenReturn(testOrder); + + // When + assertDoesNotThrow(() -> opsOrderService.assignOrder(assignReq, OperatorTypeEnum.ADMIN, 1002L)); + + // Then + verify(orderStateMachine, times(1)).transition( + eq(testOrder), + eq(WorkOrderStatusEnum.DISPATCHED), + eq(OperatorTypeEnum.ADMIN), + eq(1002L), + eq("从队列分配") + ); + assertEquals(2001L, testOrder.getAssigneeId()); + } + }