订单全链路自动化测试方案
版本: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. 测试分层架构
flowchart TB
subgraph L4["L4 可选 UI E2E"]
UI[Playwright / Cypress<br/>关键页面冒烟]
end
subgraph L3["L3 API 全链路 E2E(推荐主战场)"]
E2E[OrderLifecycleE2ETests<br/>真实 MySQL + TestServer]
end
subgraph L2["L2 API 集成"]
INT[SimpleAdmin.Api.IntegrationTests<br/>路由注册 + Mock 依赖]
end
subgraph L1["L1 单元测试"]
UNIT[Oms.Tests<br/>计费 / 签名 / 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 或环境变量)
{
"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 数据库初始化顺序
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 登录与请求头
POST /admin/auth/login
Content-Type: application/json
{ "userName": "e2e_customer", "password": "..." }
后续请求:
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 流程总览
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)
{
"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 入库 / 出库请求模板
// 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
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 客户财务
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 仓储记录
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 推荐新建项目结构(后端)
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 示例测试类骨架(伪代码)
[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 流水线设计
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 示例(片段)
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)
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 分钟)
自动化未就绪时,可按此手工验证主链路:
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 |
运行方式:
$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 必跑集。