# 订单全链路自动化测试方案 > **版本**:v1.0 > **日期**:2026-07-08 > **适用范围**:OMS 订单从创建 → 预报 → 入库 → 支付 → 出库 → 轨迹 → 签收(设计态) > **关联文档**:`oms系统设计-050801.md` §4、§6、§10.3;`物流下单协议.md`;`订单批量处理与异步队列方案0707.md` --- ## 1. 目标与范围 ### 1.1 目标 建立一套**可重复执行、可接入 CI** 的自动化测试流程,覆盖订单主 happy path: ``` 下单预报 → 入库收货 → 客户支付 → 出库发货 → 轨迹同步 → 签收完成 ``` 并验证各阶段: - **订单状态**(`OrderStatus`)与时间字段(`ReceiveTime` / `PayTime` / `ShipTime` / `SignTime`) - **费用**(预报 / 入库 / 出库阶段 `t_order_fee`) - **客户余额与流水**(`t_customer.Balance`、`t_customer_account_flow`) - **仓储操作记录**(`t_inbound_record` / `t_outbound_record`) - **轨迹服务联动**(UpsertSmallBag,可 mock) - **权限与数据范围**(客户只能看到自己的订单) ### 1.2 当前实现边界(必读) | 阶段 | 状态码 | 后端是否已实现 | 自动化策略 | |------|--------|----------------|------------| | 小包创建 + 预报计费 | `predeclared` | ✅ | API 断言 | | 入库扫描 | `pendingPayment` | ✅ | API 断言 | | 支付 | `pendingShip` | ✅ | API + 余额断言 | | 出库扫描 | `pendingOnline` | ✅ | API 断言 | | 运输中 | `shipping` | ❌ 无写入逻辑 | **Phase 2**:mock 轨迹回调或手工 DB 置态 | | 派送成功(签收) | `deliverySuccess` + `SignTime` | ❌ 无写入逻辑 | **Phase 2**:同上;或 Track 服务 `UpdateOrderStatus` | | 问题件 / 理赔件 | `problematic` / `claimed` | ✅ | 分支用例 | > **结论**:Phase 1 自动化覆盖到 **`pendingOnline`(待上网)** 为「OMS 内闭环」;签收段在 OMS 回写接口落地前,用 **Track 侧断言 + 预留 OMS 状态同步测试桩** 衔接。 --- ## 2. 测试分层架构 ```mermaid flowchart TB subgraph L4["L4 可选 UI E2E"] UI[Playwright / Cypress
关键页面冒烟] end subgraph L3["L3 API 全链路 E2E(推荐主战场)"] E2E[OrderLifecycleE2ETests
真实 MySQL + TestServer] end subgraph L2["L2 API 集成"] INT[SimpleAdmin.Api.IntegrationTests
路由注册 + Mock 依赖] end subgraph L1["L1 单元测试"] UNIT[Oms.Tests
计费 / 签名 / Batch 治理] end L1 --> L2 --> L3 L3 -.-> L4 ``` | 层级 | 项目位置 | 依赖 | 运行频率 | |------|----------|------|----------| | L1 单元 | `oms0414-back/src/Oms.Tests` | 无 DB | 每次 PR | | L2 集成 | `oms0414-back/src/SimpleAdmin.Api.IntegrationTests` | TestServer + 内存 RBAC | 每次 PR | | L3 API E2E | **待建** `Oms.E2E.Tests` 或扩展现有 IntegrationTests | Docker MySQL + 可选 RabbitMQ/WireMock | 每日 / 合并前 | | L4 UI E2E | **待建** `oms0414/e2e/` | 前端 dev + 后端 API | 发版前手动 / 夜间 | --- ## 3. 测试环境与配置 ### 3.1 基础服务 | 组件 | 用途 | 本地 / CI 建议 | |------|------|----------------| | MySQL 8.x | 业务库 `oms_test` | `docker compose` 独立库,每次 E2E 前 migrate | | SimpleAdmin.Api | OMS 后端 | `ASPNETCORE_ENVIRONMENT=Testing` | | 前端 Vite | UI E2E 可选 | `localhost:5173` | | RabbitMQ | 批量任务 E2E | 默认 **关闭**;批量专项用例单独 job 开启 | | WireMock / MockServer | Track、itfx 下游 | E2E 默认 mock,避免外网依赖 | ### 3.2 后端配置项(`appsettings.Testing.json` 或环境变量) ```json { "ConnectionStrings": { "Default": "Server=127.0.0.1;Port=3306;Database=oms_test;..." }, "OrderBatch": { "RabbitMq": { "Enabled": false } }, "Oms": { "TrackService": { "Enabled": true, "BaseUrl": "http://127.0.0.1:9090", "AppKey": "TEST_KEY", "AppSecret": "TEST_SECRET", "TimeoutSeconds": 5 }, "Itfx": { "BaseUrl": "http://127.0.0.1:9091", "TimeoutSeconds": 5 } }, "App": { "SendStartupNotification": false } } ``` | 配置节 | 测试关注点 | Mock 策略 | |--------|------------|-----------| | `Oms:TrackService` | 创建订单后异步 `UpsertSmallBag` | WireMock 返回 200 + 固定 body | | `Oms:Itfx` | 下游预报转发(可选步骤) | WireMock 或跳过 | | `OrderBatch:RabbitMq` | 批量支付/入库等 | 主链路用同步 API;批量单独用例开启 | ### 3.3 数据库初始化顺序 ```text 1. db.sql # 全量表结构 2. sql/migrate_*.sql # 增量迁移(issue_template、xhs 等按需) 3. scripts/order_batch_job_tables.sql 4. sql/seed_e2e_order_lifecycle.sql # 【待建】E2E 专用主数据(见 §4) ``` --- ## 4. 主数据 Fixture(测试前置条件) 自动化执行前,库中须存在以下**稳定可引用**的数据。建议新增 `sql/seed_e2e_order_lifecycle.sql`,所有 E2E 用例共用同一套编码。 ### 4.1 主数据清单 | 类别 | 表 / 模块 | 测试用固定值 | 说明 | |------|-----------|--------------|------| | 国家 | `t_country` | `US` / `CN` | 收件国用于分区计价 | | 客户 | `t_customer` | `customerCode=E2E001`,`Balance >= 10000` | 支付前须余额充足 | | RBAC 用户 | `user` | 客户主账号,绑定 `E2E001` | 登录拿 JWT | | 仓库 | `t_warehouse_address` | `warehouseId=9001` | 下单、扫描共用 | | 销售产品 | `t_sales_product` | `productCode=E2E-SP-01` | 已发布、已绑定规则/价格快照 | | 服务渠道 | `t_service_channel` | `channelCode=E2E-CH-01` | 排货后可写入订单 | | 价格 / 分区 | `t_sales_product_price*`、`t_kpi_area*` | 覆盖 US 分区 | 否则创建订单会 `draft` + `PredeclareFailReason` | | 计重 / 进位 | `t_weight_rule`、`t_weight_rounding` | 匹配 1kg 标准件 | 入库/outbound 计费依赖 | | 数据权限 | 数据范围 | 测试用户可见 E2E 客户 | 否则列表为空 | ### 4.2 登录与请求头 ```http POST /admin/auth/login Content-Type: application/json { "userName": "e2e_customer", "password": "..." } ``` 后续请求: ```http Authorization: Bearer {accessToken} ``` 客户会话下单时 **不传 `customerId`**(由 `OmsSessionHelper` 解析);内部运营账号测入库/出库时用 **运营 RBAC 账号**。 ### 4.3 推荐双角色 | 角色 | 用途 | 权限前缀 | |------|------|----------| | `e2e_customer` | 下单、支付、查单 | `Order.PlaceOrder.*`、`Order.MyOrders.*` | | `e2e_operator` | 入库、出库扫描 | `Operation.InboundScan.*`、`Operation.OutboundScan.*` | --- ## 5. 主流程用例设计(Happy Path) ### 5.1 流程总览 ```mermaid sequenceDiagram participant C as 客户 API participant O as OMS OrderService participant W as 仓储 Operation participant F as Finance participant T as Track(Mock) C->>O: POST /order/parcel/create O-->>C: predeclared + serviceNo O->>T: UpsertSmallBag (async) C->>W: POST /operation/inbound/create W->>O: ApplyReceiveBilling W-->>C: pendingPayment + ReceiveTime C->>O: POST /order/pay O->>F: DebitOrderPay O-->>C: pendingShip + PayTime C->>W: POST /operation/outbound/create W->>O: ApplyShipBilling W-->>C: pendingOnline + ShipTime Note over C,T: Phase 2 签收 T-->>O: (待实现) 状态回写 O-->>C: deliverySuccess + SignTime ``` ### 5.2 分步用例表 | 步骤 | API | 方法 | 关键入参 | 期望 `OrderStatus` | 期望副作用 | |------|-----|------|----------|-------------------|------------| | 0 可选试算 | `/order/price-check` | POST | `productCode`, 件重尺, 收件国 | — | 返回价格 > 0 | | 1 创建订单 | `/order/parcel/create` | POST | 见 §5.3 | `predeclared` | `t_order_fee` 预报应收;`ServiceNo` 生成 | | 2 查详情 | `/order/detail` | GET | `id` | `predeclared` | 发件人/收件人/申报齐全 | | 3 入库 | `/operation/inbound/create` | POST | `waybillNo`, 实重尺 | `pendingPayment` | `ReceiveTime` 非空;入库费 `ChargingStage=inbound` | | 4 支付 | `/order/pay` | POST | `{ "id": orderId }` | `pendingShip` | `IsPaid=1`, `PayTime`;余额减少;`account_flow` | | 5 出库 | `/operation/outbound/create` | POST | `waybillNo`, 实重尺 | `pendingOnline` | `ShipTime` 非空;出库费 | | 6 列表 | `/order/list` | POST | `status=pendingOnline` | — | 包含该 `serviceNo` | | 7 轨迹 | Mock 校验 | — | — | — | UpsertSmallBag 被调用 1 次 | | 8 签收 | **Phase 2** | — | — | `deliverySuccess` | `SignTime` 非空 | ### 5.3 创建订单请求模板(E2E Fixture) ```json { "requestId": "e2e-{uuid}", "clientNo": "E2E-CLIENT-{timestamp}", "productCode": "E2E-SP-01", "cargoAttribute": "general", "warehouseId": 9001, "length": 30, "width": 20, "height": 10, "actualWeight": 1.2, "quantity": 1, "receiver": { "name": "E2E Receiver", "phone": "13800000001", "countryCode": "US", "state": "CA", "city": "Los Angeles", "address": "100 Test St", "postcode": "90001" }, "sender": { "name": "E2E Sender", "phone": "13900000001", "countryCode": "CN", "city": "Shenzhen", "address": "Test Warehouse" }, "items": [ { "nameEn": "Test Goods", "nameCn": "测试商品", "quantity": 1, "unitPrice": 10, "weight": 1.2, "hsCode": "6109100021" } ] } ``` ### 5.4 入库 / 出库请求模板 ```json // POST /operation/inbound/create { "waybillNo": "{serviceNo}", "weight": 1.2, "length": 30, "width": 20, "height": 10, "warehouseId": 9001 } // POST /operation/outbound/create { "waybillNo": "{serviceNo}", "weight": 1.2, "length": 30, "width": 20, "height": 10, "warehouseId": 9001 } ``` --- ## 6. 功能模块与 API 映射 | 业务阶段 | 后端 Service | 前端模块 | 路由 | 权限(`pageButtonCatalog`) | |----------|--------------|----------|------|---------------------------| | 试算 / 下单 | `OrderService` | 物流下单 | `/order/placeOrder` | `Order.PlaceOrder.*` | | 订单管理 | `OrderService` | 我的订单 | `/order/myOrders` | `Order.MyOrders.*` | | 入库 | `InboundRecordService` | 入库扫描 | `/operation/inboundScan` | `Operation.InboundScan.Inbound` | | 出库 | `OutboundRecordService` | 出库扫描 | `/operation/outboundScan` | `Operation.OutboundScan.Outbound` | | 客户余额 | `CustomerBalanceMutator` | 客户管理 / 充值 | `/user/customer` | `User.Customer.*` | | 应收应付 | `FinanceService` | 应收应付 | `/finance/receivablePayable` | `Finance.ReceivablePayable.Query` | | 账户流水 | `CustomerAccountFlowService` | — | API | — | | 渠道配置 | `SalesProductService` 等 | 销售产品 / 服务渠道 | `/channel/*` | 各模块 Query/Edit | | 轨迹推送 | `SmallBagTrackOrderNotifier` | 我的订单-创建轨迹 | — | `Order.MyOrders.CreateTrackOrder` | | 下游预报 | `OrderService.DownstreamForward` | 转发下游 | — | `Order.MyOrders.ForwardDownstreamCreate` | | 批量任务 | `OrderBatchJobCoordinator` | 批量任务 | `/order/batchJobs` | `Order.BatchJobs.*` | --- ## 7. 断言检查点(每层必验) ### 7.1 HTTP 层 - 状态码 200(业务错误时 Fake 框架返回统一错误体,需断言 `message` 为空或 success 标志) - 响应 JSON 关键字段类型与枚举值 ### 7.2 订单主表 `t_order` ```sql SELECT OrderStatus, IsPaid, ReceiveTime, PayTime, ShipTime, SignTime, PredeclareFailReason, ChannelCode, ChargeableWeight FROM t_order WHERE ServiceNo = @serviceNo; ``` | 步骤后 | OrderStatus | IsPaid | 时间字段 | |--------|-------------|--------|----------| | 创建 | `predeclared` | 0 | 全 NULL | | 入库 | `pendingPayment` | 0 | ReceiveTime 有值 | | 支付 | `pendingShip` | 1 | PayTime 有值 | | 出库 | `pendingOnline` | 1 | ShipTime 有值 | | 签收 | `deliverySuccess` | 1 | SignTime 有值 | ### 7.3 费用 `t_order_fee` - 预报后:存在 `Type=receivable` 且 `ChargingStage` 含 forecast/predeclare - 入库后:新增 `ChargingStage=inbound` 行(若产品配置入库计费) - 出库后:新增 `ChargingStage=outbound` 行 - 支付金额 = 当前应收合计 ### 7.4 客户财务 ```sql SELECT Balance FROM t_customer WHERE Id = @customerId; SELECT * FROM t_customer_account_flow WHERE BizType = 'orderPay' AND BizId = @orderId; ``` - 支付后:`Balance` 减少额 = 订单应收合计 - 流水 `Amount` 为负,`BalanceBefore/After` 连贯 ### 7.5 仓储记录 ```sql SELECT * FROM t_inbound_record WHERE WaybillNo = @serviceNo; SELECT * FROM t_outbound_record WHERE WaybillNo = @serviceNo; ``` ### 7.6 轨迹 Mock - WireMock 收到 `POST /OrderV2/UpsertSmallBag` - 请求头含 `appKey`、`timestamp`、`sign`(签名算法见 `SmallBagTrackSignHelperTests`) --- ## 8. 负向与分支用例(摘要) | 编号 | 场景 | 触发 | 期望 | |------|------|------|------| | N01 | 余额不足支付 | 客户 Balance < 应收 | `账户余额不足` | | N02 | 未入库支付 | 跳过步骤 3 直接 pay | `当前订单状态不可支付` | | N03 | 未支付出库 | 跳过步骤 4 直接 outbound | `订单未付款,禁止出库` | | N04 | 重复支付 | pay 两次 | `订单已支付` | | N05 | 价格未配置 | 错误 productCode | `draft` + `PredeclareFailReason` | | N06 | 黑名单国家/客户 | 命中黑名单 | 创建失败,不落库或 draft | | N07 | 拦截单出库 | intercept 后 outbound | 按业务规则拒绝或警告 | | B01 | 问题件流程 | mark-problematic | `problematic` + `handleStatus=pending` | | B02 | 处理完成 | handle-status/update | `handleStatus=processed` | | B03 | 批量支付 | `/order/batch/pay` + RabbitMQ | Job 全部 success(独立 job) | --- ## 9. 自动化实现建议 ### 9.1 推荐新建项目结构(后端) ```text oms0414-back/ src/ Oms.E2E.Tests/ # 新建 Fixtures/ OrderLifecycleFixture.cs # 登录、种子数据、清理 WireMockTrackServer.cs Scenarios/ OrderLifecycleHappyPathTests.cs OrderLifecycleNegativeTests.cs sql/ seed_e2e_order_lifecycle.sql assert_order_state.sql appsettings.E2E.json ``` ### 9.2 技术选型 | 能力 | 库 | |------|-----| | API 宿主 | 复用 `SimpleAdminApiTestHost` 或 `WebApplicationFactory` + 真实 MySQL | | DB 断言 | `Dapper` / 直接 `SqlSugar` 查询 | | HTTP 客户端 | `HttpClient` + `System.Text.Json` | | Mock 外服 | `WireMock.Net` | | 测试框架 | xUnit + FluentAssertions(与现有一致) | ### 9.3 示例测试类骨架(伪代码) ```csharp [Collection("OrderLifecycle")] public class OrderLifecycleHappyPathTests { [Fact] public async Task Step01_To_Step05_PendingOnline_ShouldSucceed() { var token = await Fixture.LoginAsCustomerAsync(); var create = await Fixture.Api.PostJsonAsync("/admin/order/parcel/create", body, token); create.OrderStatus.Should().Be("predeclared"); var serviceNo = create.ServiceNo; await Fixture.Api.PostJsonAsync("/admin/operation/inbound/create", inboundBody, operatorToken); await Fixture.AssertOrderStatusAsync(serviceNo, "pendingPayment"); await Fixture.Api.PostJsonAsync("/admin/order/pay", new { id = create.Id }, token); await Fixture.AssertOrderStatusAsync(serviceNo, "pendingShip"); await Fixture.AssertBalanceDebitedAsync(create.CustomerId, create.Id); await Fixture.Api.PostJsonAsync("/admin/operation/outbound/create", outboundBody, operatorToken); await Fixture.AssertOrderStatusAsync(serviceNo, "pendingOnline"); Fixture.TrackMock.VerifyUpsertCalledOnce(serviceNo); } } ``` ### 9.4 数据隔离策略 | 策略 | 做法 | |------|------| | **A 每用例独立 clientNo** | `E2E-{guid}`,不删库,靠唯一键隔离(推荐) | | **B 事务回滚** | SqlSugar 难包裹跨 HTTP 请求,不推荐 | | **C 每 job 重建库** | CI 慢但最干净;夜间全量 E2E 使用 | --- ## 10. CI/CD 流水线设计 ```mermaid flowchart LR PR[PR / Push] --> L1[dotnet test Oms.Tests] L1 --> L2[dotnet test IntegrationTests] L2 --> E2E{main 分支?} E2E -->|是| Docker[启动 MySQL + WireMock] Docker --> Seed[执行 seed_e2e_order_lifecycle.sql] Seed --> L3[dotnet test Oms.E2E.Tests] L3 --> Report[测试报告 / 飞书通知] ``` ### 10.1 GitHub Actions 示例(片段) ```yaml jobs: unit-and-integration: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: dotnet test src/Oms.Tests - run: dotnet test src/SimpleAdmin.Api.IntegrationTests order-lifecycle-e2e: if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest services: mysql: image: mysql:8.0 env: MYSQL_ROOT_PASSWORD: test MYSQL_DATABASE: oms_test steps: - run: mysql -h 127.0.0.1 -uroot -ptest oms_test < db.sql - run: mysql -h 127.0.0.1 -uroot -ptest oms_test < sql/seed_e2e_order_lifecycle.sql - run: dotnet test src/Oms.E2E.Tests --filter Category=E2E ``` ### 10.2 本地一键脚本(建议 `scripts/run-order-e2e.ps1`) ```powershell docker compose -f docker-compose.e2e.yml up -d dotnet test src/Oms.E2E.Tests docker compose -f docker-compose.e2e.yml down ``` --- ## 11. Phase 2:签收段衔接方案 OMS 尚未实现 `shipping` → `deliverySuccess` 回写时,任选其一: | 方案 | 说明 | 适用 | |------|------|------| | **P2-A Track 侧完成** | 调 `trackApi/Track/Order/UpdateOrderStatus` 置 `isCompleted=true` | 验证轨迹系统闭环 | | **P2-B 测试专用 API** | 新增 `[TestingOnly] POST /order/test/mark-delivered` | 仅 Testing 环境注册 | | **P2-C SQL 置态** | E2E 直接 `UPDATE t_order SET OrderStatus='deliverySuccess', SignTime=NOW()` | 临时;不验 API | | **P2-D 实现正式回调** | Track Webhook → OMS Consumer | 生产方案,纳入同一 E2E | **推荐路径**:先 **P2-B** unblock E2E,上线前替换为 **P2-D** 并删除测试 API。 --- ## 12. 实施路线图 | 阶段 | 交付物 | 工时估 | |------|--------|--------| | **P0** | 本文档 + `seed_e2e_order_lifecycle.sql` | 1d | | **P1** | `Oms.E2E.Tests` Happy Path(至 pendingOnline) | 2–3d | | **P1** | WireMock Track + 负向用例 N01–N04 | 1d | | **P2** | 签收置态 / Webhook + 完整端到签收 | 2d | | **P3** | Playwright 冒烟(下单页 + 扫描页) | 2d | | **P3** | CI nightly + 测试报告 | 1d | --- ## 13. 关键代码与文档索引 ### 后端 | 路径 | 说明 | |------|------| | `Oms.Domain/OrdersAggregate/Consts/OrderStatusCodes.cs` | 订单状态枚举 | | `Oms.Application/Services/Orders/OrderService.ParcelBilling.cs` | 创建、入库/出库计费 | | `Oms.Application/Services/Orders/OrderService.cs` | 支付、列表 | | `Oms.Application/Services/Operations/InboundRecordService.cs` | 入库 | | `Oms.Application/Services/Operations/OutboundRecordService.cs` | 出库 | | `Oms.Application/Shared/CustomerBalanceMutator.cs` | 支付扣款 | | `Oms.Application/Integrations/SmallBagTrack/` | 轨迹推送 | | `SimpleAdmin.Api.IntegrationTests/Support/SimpleAdminApiTestHost.cs` | 集成测试宿主 | | `Oms.Tests/ParcelBillingCalculatorTests.cs` | 计费单元测试 | ### 前端 | 路径 | 说明 | |------|------| | `src/constants/orderStatus.ts` | 状态常量 | | `src/api/order/index.ts` | 订单 API | | `src/api/operation/index.ts` | 仓储 API | | `src/views/order/placeOrder/index.vue` | 下单页 | | `src/views/operation/inboundScan/index.vue` | 入库 | | `src/views/operation/outboundScan/index.vue` | 出库 | ### 文档 | 路径 | 说明 | |------|------| | `docs/oms系统设计-050801.md` | 主设计(§4 订单、§6 操作、§10.3 状态码) | | `docs/物流下单协议.md` | parcel/create 协议 | | `docs/订单批量处理与异步队列方案0707.md` | 批量异步 | --- ## 14. 附录:手动冒烟检查清单(5 分钟) 自动化未就绪时,可按此手工验证主链路: - [ ] 客户登录,余额 ≥ 100 - [ ] 销售产品 `E2E-SP-01` 已发布且 US 有价格 - [ ] `POST /order/parcel/create` → 返回 `predeclared` - [ ] `POST /operation/inbound/create` → 订单变 `pendingPayment` - [ ] `POST /order/pay` → `pendingShip`,余额减少 - [ ] `POST /operation/outbound/create` → `pendingOnline` - [ ] `GET /order/detail` → `ReceiveTime`、`PayTime`、`ShipTime` 均有值 - [ ] (可选)Track Mock 日志有 UpsertSmallBag --- ## 15. 已落地实现(2026-07-08) | 交付物 | 路径 | |--------|------| | E2E 主数据种子 | `oms0414-back/sql/seed_e2e_order_lifecycle.sql` | | E2E 测试项目 | `oms0414-back/src/Oms.E2E.Tests/` | | Happy Path 用例 | `Scenarios/OrderLifecycleHappyPathTests.cs` | | 一键脚本 | `oms0414-back/scripts/run-order-e2e.ps1` | **运行方式:** ```powershell $env:OMS_E2E_ENABLED = "1" $env:OMS_E2E_CONNECTION = "Server=127.0.0.1;Port=3306;Database=oms;Uid=root;Pwd=你的密码;CharSet=utf8mb4;" cd oms0414-back .\scripts\run-order-e2e.ps1 ``` 未设置 `OMS_E2E_ENABLED` 时,E2E 用例自动跳过(不失败)。 --- **维护说明**:OMS 实现签收回写后,更新 §1.2 边界表、§5 步骤 8、§11,并将 Phase 2 用例并入 P1 必跑集。