Files
railwaysystem/docs/development/dms-r3/11_testing_and_mock_plan.md
T

7.8 KiB
Raw Blame History

11 模拟测试体系、并发验证与测试沙箱要求 (Testing and Mock Plan)

测试最高铁律:严禁在持续集成 (CI/CD) 或单元测试中使用真实 DMS 生产环境与真实门锁设备! 门锁下发密码将直接修改物理设备存储并消耗锁端电量与无线频段。 必须基于 本地 Mock HTTP Server (WireMock / MockWebServer) 搭建完备的多层级可复现测试套件。


1. 分层测试策略体系 (Testing Strategy)

+--------------------------------------------------------------------------------+
| L4. 现场沙箱验收测试 (Live Sandbox E2E - 独立测试账号 + 实验台独立挂载测试锁)       |
+--------------------------------------------------------------------------------+
| L3. 契约与故障注入测试 (WireMock Failure Injection - 401/超时/宕机/乱序状态)    |
+--------------------------------------------------------------------------------+
| L2. 并发与状态机集成测试 (Concurrency & State Machine Test - 20并发Token竞争/幂等) |
+--------------------------------------------------------------------------------+
| L1. 纯领域单元测试 (Unit Test - CardNumberCodec 编码 / 状态机转换 / DTO 转换)     |
+--------------------------------------------------------------------------------+

2. Mock DMS 仿真覆盖矩阵 (22 项核心场景)

在后续 R.3B 实现阶段,集成测试套件必须基于 Mock 服务器完整覆盖以下 22 项场景:

# 测试场景用例 Mock 桩行为定义 预期系统断言与表现
1 Login Success 返回 200,包含新 Token 与 expiresIn: 7200 DmsTokenManager 缓存 Token,设置正确到期时间戳
2 Token Cache Reuse 连续 10 次请求同一接口 仅在第 1 次触发 Mock 登录,后续 9 次请求 Header 均复用相同 Token
3 Token Expiry 模拟系统时钟快进至失效时间前 5 分钟 自动触发平滑预防性刷新,获取新 Token
4 Concurrent Token Refresh 20 个并发业务线程同时探测到 Token 缺失/过期 单飞排他锁生效:Mock 登录接口被且仅被调用 1 次,20 个线程全部安全复用该 Token,无踩踏覆盖
5 401 Auto Relogin 业务请求收到 Mock 返回 401 Unauthorized 自动使缓存失效,触发单飞重登录,并自动安全重试原请求 1 次且成功
6 Room Lookup Normal 精确匹配返回单个房间 placeId: 1320 正常持久化映射记录
7 Room Lookup Not Found 返回 {"success": false, "message": "Room not found!"} 抛出 RoomNotFoundException,不阻塞其他业务
8 Room Multiple Results 模糊查询返回同名房间(多楼层同房号) 触发精准过滤,若仍有多条抛出歧义异常提醒管理员人工介入
9 Lock Lookup Normal 返回房间挂载的 1 把锁 绑定该锁并派发授权
10 Lock Lookup Multiple 返回房间挂载的 2 把锁(外门+内门) 一房多锁验证:为该住宿安排分别生成 2 条授权记录并同时下发
11 Lock Lookup None 返回空数组(该房间未装门锁) 抛出 LockNotConfiguredException,状态标记为无锁,不影响入住
12 Password Issue Accepted 调用 create 返回 200 + operationId 本地授权状态置为 ISSUING,操作记录置为 ACCEPTED,不得直接置为 ACTIVE
13 Password Operation Success 10 秒后轮询返回 operationStatus: "success" 授权状态正式推进为 ACTIVE,操作记录置为 SUCCESS
14 Password Operation Undone 首次轮询返回 operationStatus: "undone" 保持 ISSUING 状态,触发第 2 次退避轮询
15 Password Operation Failed 轮询返回 operationStatus: "failed" 授权状态置为 ISSUE_FAILED,记录详细错误原因
16 Password Operation Timeout 轮询返回 operationStatus: "timeout" 轮询上限后停止,状态置为 ISSUE_FAILED (TIMEOUT),页面显示重试按钮
17 Card Issue & Codec 下发 10 进制卡号 3161082660 验证 Mock 实际收到的 Body 中 cardNumber 为小端序 244B6ABC
18 Revoke Flow 调房或退宿时调用 delete 接口 正确携带 external_key_id,轮询成功后状态置为 REVOKED
19 DMS Unavailable Mock 服务断开或返回 HTTP 500/502 住宿业务事务正常提交完成,门禁状态置为 ISSUE_FAILED,记录故障日志
20 Unknown Result Reconcile 发送 create 时遭遇网络读取超时 触发未决对账流程,查询当前房间钥匙列表,避免重复发码
21 Idempotent Duplicate Grant 连续快速点击 5 次“下发权限” 本地幂等生效,仅派发 1 次 DMS 请求,无重复钥匙生成
22 Accommodation Transfer 学员换房(旧房 A -> 新房 B) 验证“先发新房 B,确认成功后再撤销旧房 A”的时序与失败补偿

3. 关键专项验证测试设计细节

3.1 Token 并发竞争专项测试设计 (Token Concurrency Test)

// 测试伪代码设计示意 (后续 R.3B 落地至 src/test/java)
@Test
public void testConcurrentLoginDeduplication() throws Exception {
    int threadCount = 20;
    CountDownLatch startLatch = new CountDownLatch(1);
    CountDownLatch doneLatch = new CountDownLatch(threadCount);
    ExecutorService executor = Executors.newFixedThreadPool(threadCount);

    // 清空当前 Token 缓存
    dmsTokenManager.invalidate();

    for (int i = 0; i < threadCount; i++) {
        executor.submit(() -> {
            try {
                startLatch.await(); // 确保 20 个线程在瞬间同时并发冲击
                String token = dmsTokenManager.getToken();
                assertThat(token).isNotBlank();
            } finally {
                doneLatch.countDown();
            }
        });
    }

    startLatch.countDown(); // 发令枪响
    doneLatch.await(10, TimeUnit.SECONDS);

    // 严苛断言:Mock 服务端的登录接口在整个并发过程中被调用的次数必须精确为 1!
    wireMockServer.verify(1, postRequestedFor(urlEqualTo("/public/rest/third-party-app/token/get")));
}

3.2 异步状态生命周期防抢跑断言 (Async Anti-Premature Test)

  • 断言目标:杜绝将 HTTP 200 直接视为下发成功。
  • 验证步骤:
    1. 调用 doorAccessGateway.grantAccess(...)。
    2. 立即检查数据库中的 train_dorm_auth 记录:
      • 断言 auth_status == ISSUING(下发中)。
      • 断言绝不能等于 ACTIVE。
    3. 执行一次调度轮询(此时 Mock 返回 operationStatus = undone):
      • 断言 auth_status 依然维持在 ISSUING。
    4. 执行第二次调度轮询(此时 Mock 返回 operationStatus = success):
      • 断言 auth_status 顺利跃迁为 ACTIVE。

4. 生产环境接入前联调沙箱要求 (DMS_SANDBOX_REQUIREMENTS)

在未来正式将 RailwaySystem 部署到沈阳房建公寓段并对接真实门锁前,必须向 DMS 供应商及公寓设备管理方书面申请独立测试沙箱环境。

4.1 核心沙箱准入条件

  1. 独立测试租户:必须提供专属的测试用 appid、appsecret 及 projectId,严禁与正在运行的既有公寓楼共用租户。
  2. 物理实验台测试锁具:
    • 必须在实验台/机房单独架设 至少 1 台测试网关 + 2 把独立挂载的测试智能门锁。
    • 严禁直接使用学员已经实际入住的宿舍门锁进行研发调试!
  3. 测试数据隔离:
    • 在 DMS 中建立虚拟楼栋(如“联调测试楼”),配置测试房间(如“9901室”、“9902室”)。
  4. 技术支撑配合:
    • 供应商提供联调期间的技术接口人,并确认正式环境网络是否具备固定公网 IP 白名单或专网通道。