WMS测试模式Skill wms-testing-patterns

这个技能专注于仓库管理系统(WMS)的测试模式,涵盖库存操作验证、EDI文档处理、RF扫描流程和WMS-ERP集成测试。关键词:WMS测试,仓库管理,EDI,RF扫描,ERP集成,软件测试,Blue Yonder,Manhattan,SAP EWM。

测试 0 次安装 0 次浏览 更新于 3/9/2026

name: wms-testing-patterns description: “仓库管理系统测试模式,涵盖库存操作、拣货/包装/发货流程、波次管理、EDI X12/EDIFACT 合规性、RF/条码扫描以及 WMS-ERP 集成。适用于测试 WMS 平台(Blue Yonder、Manhattan、SAP EWM)。” category: 企业集成 priority: 高 tokenEstimate: 1800 agents: [qe-middleware-validator, qe-contract-validator, qe-sap-idoc-tester, qe-odata-contract-validator] implementation_status: 优化 optimization_version: 2.0 last_optimized: 2026-02-04 dependencies: [api-testing-patterns, contract-testing, enterprise-integration-testing] quick_reference_card: true tags: [wms, warehouse, inventory, edi, pick-pack-ship, blue-yonder, manhattan, sap-ewm, rf-scanning] trust_tier: 3 validation: schema_path: schemas/output.json validator_path: scripts/validate-config.json eval_path: evals/wms-testing-patterns.yaml

WMS 测试模式

<default_to_action> 测试仓库管理系统时:

  1. 验证库存准确性(收货、上架、周期盘点、调整)
  2. 测试拣货/包装/发货端到端流程(波次释放 -> 发货确认)
  3. 验证EDI消息处理(856 发货通知、940 订单、945 确认、943/944 库存)
  4. 断言RF/条码扫描流程(扫描 -> 验证 -> 更新 -> 确认)
  5. 执行分配和补货逻辑(FIFO、FEFO、批次控制)
  6. 测试WMS-ERP集成(库存同步、订单状态、货物收货)
  7. 验证波次管理(波次计划、释放、短拣处理)

快速模式选择:

  • 库存差异 -> 周期盘点和调整测试
  • 订单履行问题 -> 拣货/包装/发货流程测试
  • EDI故障 -> 消息格式和确认测试
  • 扫描问题 -> RF设备模拟测试
  • 分配错误 -> 批次控制和FIFO/FEFO测试
  • 集成缺口 -> WMS-ERP同步边界测试

关键成功因素:

  • WMS准确性直接影响客户体验和财务报告
  • 始终测试正常流程和异常流程(短拣、损坏货物、退货)
  • EDI测试必须覆盖语法验证和业务规则验证 </default_to_action>

快速参考卡

何时使用

  • 测试WMS平台(Blue Yonder/JDA、Manhattan Associates、SAP EWM、Oracle WMS)
  • 验证库存交易准确性(收货、拣货、调整)
  • 测试EDI文档交换(X12 856/940/945/943/944、EDIFACT DESADV/ORDERS)
  • 验证RF/移动扫描流程
  • 测试波次管理和分配逻辑
  • 验证WMS到ERP集成(SAP MM/WM、Oracle)

测试级别

级别 目的 依赖 速度
单元逻辑 分配/补货规则
API合同 WMS REST/SOAP端点合同 API桩
EDI验证 文档语法 + 业务规则 EDI解析器
集成 WMS-ERP库存同步 完整堆栈 较慢
端到端流程 完整订单履行周期 WMS + ERP + TMS

关键测试场景

场景 必须测试 示例
收货 入库准确性 PO收货,带超/欠交货容差
上架 位置分配 基于产品属性的定向上架
拣货 波次/批次拣货 多订单波次,带优先级分配
包装 包装验证 纸箱化和重量/尺寸验证
发货 承运商分配 费率比价和标签生成
周期盘点 库存准确性 盲盘 vs. 引导盘点对账
退货 逆向物流 RMA收货、检查、处置
EDI 856 ASN生成 发货确认触发正确的ASN给客户
EDI 940 仓库订单 入库订单创建正确的工作订单
短拣 异常处理 部分分配,创建缺货订单

工具

  • WMS平台:Blue Yonder WMS、Manhattan WMOS、SAP EWM、Oracle WMS Cloud
  • EDI测试:Bots EDI、SPS Commerce Test、Cleo Clarify
  • RF模拟:自定义RF模拟器、Zebra SDK测试工具
  • 集成:SAP PI/PO、MuleSoft、Dell Boomi
  • 监控:Splunk、WMS仪表板、EDI跟踪门户

代理协调

  • qe-middleware-validator:WMS-ERP集成流程和消息转换验证
  • qe-contract-validator:EDI文档合同和WMS API合同
  • qe-sap-idoc-tester:WMS-SAP IDoc验证(WMMBID01、SHPCON)
  • qe-odata-contract-validator:SAP EWM OData服务测试

库存交易测试

收货和上架

describe('入库收货 - 带批次控制的PO', () => {
  it('接收带有批次跟踪的货物针对PO', async () => {
    const receipt = await wms.receive({
      po: 'PO-4500001234',
      item: 'MAT-100',
      quantity: 100,
      lot: 'LOT-2026-001',
      expiryDate: '2027-06-30',
      uom: 'EA'
    });

    expect(receipt.status).toBe('RECEIVED');
    expect(receipt.quantityReceived).toBe(100);
    expect(receipt.lot).toBe('LOT-2026-001');

    // 验证上架任务已创建
    const putawayTask = await wms.getTask(receipt.putawayTaskId);
    expect(putawayTask.type).toBe('DIRECTED_PUTAWAY');
    expect(putawayTask.suggestedLocation).toMatch(/^BIN-/);
  });

  it('拒绝超出容差的超收货', async () => {
    // PO行是100 EA,容差是10%
    const result = await wms.receive({
      po: 'PO-4500001234',
      item: 'MAT-100',
      quantity: 115, // 15% 超,超出10%容差
      lot: 'LOT-2026-002'
    });

    expect(result.status).toBe('REJECTED');
    expect(result.reason).toContain('超收货容差超出');
    expect(result.maxAllowed).toBe(110);
  });

  it('接受欠收货并创建剩余未清数量', async () => {
    const result = await wms.receive({
      po: 'PO-4500001234',
      item: 'MAT-100',
      quantity: 80,
      lot: 'LOT-2026-003'
    });

    expect(result.status).toBe('PARTIAL_RECEIPT');
    expect(result.quantityReceived).toBe(80);
    expect(result.remainingOpen).toBe(20);
  });
});

describe('定向上架', () => {
  it('基于产品属性分配位置', async () => {
    // 危险品应去危险品区
    const putaway = await wms.executePutaway({
      item: 'MAT-HAZ-001',
      attributes: { hazmat: true, temperature: 'ambient' },
      quantity: 50,
      lot: 'LOT-HAZ-001'
    });

    expect(putaway.location).toMatch(/^HAZ-/); // 危险品区前缀
    expect(putaway.status).toBe('PUTAWAY_COMPLETE');
  });

  it('主区满时回退到溢出区', async () => {
    // 先填满主区
    await wms.fillZone('ZONE-A', 100); // 100% 容量

    const putaway = await wms.executePutaway({
      item: 'MAT-200',
      quantity: 10,
      preferredZone: 'ZONE-A'
    });

    expect(putaway.location).toMatch(/^OVF-/); // 溢出区
    expect(putaway.overflowReason).toBe('PRIMARY_ZONE_FULL');
  });
});

周期盘点和调整

describe('周期盘点 - 盲盘', () => {
  it('自动批准阈值内的差异', async () => {
    // 系统显示100 EA,物理盘点是98
    const count = await wms.submitCycleCount({
      location: 'BIN-A-01-01',
      item: 'MAT-100',
      countedQuantity: 98,
      countType: 'BLIND', // 盘点员看不到系统数量
      varianceThreshold: 5 // 百分比
    });

    expect(count.systemQuantity).toBe(100);
    expect(count.variance).toBe(-2);
    expect(count.variancePercent).toBeCloseTo(2.0);
    expect(count.autoApproved).toBe(true);
    expect(count.adjustmentPosted).toBe(true);
  });

  it('差异超出阈值时需要主管批准', async () => {
    const count = await wms.submitCycleCount({
      location: 'BIN-A-01-02',
      item: 'MAT-200',
      countedQuantity: 80,
      countType: 'BLIND',
      varianceThreshold: 5 // 20% 差异超出5%阈值
    });

    expect(count.variancePercent).toBeCloseTo(20.0);
    expect(count.autoApproved).toBe(false);
    expect(count.adjustmentPosted).toBe(false);
    expect(count.status).toBe('PENDING_SUPERVISOR_APPROVAL');
  });

  it('差异严重时触发重新盘点', async () => {
    const count = await wms.submitCycleCount({
      location: 'BIN-A-01-03',
      item: 'MAT-300',
      countedQuantity: 0, // 系统显示50
      countType: 'BLIND',
      varianceThreshold: 5,
      criticalThreshold: 50
    });

    expect(count.variancePercent).toBe(100);
    expect(count.status).toBe('RECOUNT_REQUIRED');
    expect(count.recountAssigned).toBe(true);
    expect(count.recountAssignee).not.toBe(count.originalCounter);
  });
});

拣货/包装/发货流程测试

波次管理

describe('波次计划和释放', () => {
  it('从符合条件的订单创建波次,使用批量拣货策略', async () => {
    // 为波次计划创建5个订单
    const orders = await Promise.all(
      Array.from({ length: 5 }, (_, i) => wms.createOrder({
        orderId: `SO-100${i}`,
        lines: [
          { item: 'MAT-100', qty: 10 },
          { item: 'MAT-200', qty: 5 }
        ],
        priority: i === 0 ? 'RUSH' : 'STANDARD',
        shipBy: '2026-02-05'
      }))
    );

    const wave = await wms.planWave({
      strategy: 'BATCH_PICK',
      maxOrders: 10,
      cutoffTime: '2026-02-05T14:00:00Z'
    });

    expect(wave.orderCount).toBe(5);
    expect(wave.pickTasks).toBeGreaterThan(0);
    expect(wave.status).toBe('PLANNED');

    // 加急订单应在拣货序列中优先
    const firstTask = wave.pickTasks[0];
    expect(firstTask.orderId).toBe('SO-1000');
    expect(firstTask.priority).toBe('RUSH');
  });

  it('处理短拣,创建缺货订单', async () => {
    // 仅5 EA可用,订单需要10
    await wms.setInventory('BIN-B-01-01', 'MAT-500', 5);

    const order = await wms.createOrder({
      orderId: 'SO-SHORT',
      lines: [{ item: 'MAT-500', qty: 10 }]
    });

    const wave = await wms.planWave({ strategy: 'SINGLE_ORDER' });
    await wms.releaseWave(wave.id);

    // 尝试拣货
    const pickResult = await wms.confirmPick({
      taskId: wave.pickTasks[0].id,
      pickedQty: 5 // 仅5可用
    });

    expect(pickResult.status).toBe('SHORT_PICK');
    expect(pickResult.pickedQuantity).toBe(5);
    expect(pickResult.shortQuantity).toBe(5);
    expect(pickResult.backorderCreated).toBe(true);
    expect(pickResult.backorderId).toBeDefined();
  });
});

分配逻辑

describe('FIFO分配', () => {
  it('首先分配最旧的批次', async () => {
    // 设置两个批次,收货日期不同
    await wms.receiveInventory('MAT-FIFO', 50, { lot: 'LOT-OLD', receiptDate: '2026-01-01' });
    await wms.receiveInventory('MAT-FIFO', 50, { lot: 'LOT-NEW', receiptDate: '2026-02-01' });

    const allocation = await wms.allocate({
      item: 'MAT-FIFO',
      quantity: 30,
      method: 'FIFO'
    });

    expect(allocation.allocatedLot).toBe('LOT-OLD');
    expect(allocation.quantity).toBe(30);
  });

  it('当最旧批次不足时,跨批次拆分分配', async () => {
    await wms.receiveInventory('MAT-SPLIT', 20, { lot: 'LOT-A', receiptDate: '2026-01-01' });
    await wms.receiveInventory('MAT-SPLIT', 50, { lot: 'LOT-B', receiptDate: '2026-01-15' });

    const allocation = await wms.allocate({
      item: 'MAT-SPLIT',
      quantity: 40,
      method: 'FIFO'
    });

    expect(allocation.splits).toHaveLength(2);
    expect(allocation.splits[0]).toEqual({ lot: 'LOT-A', quantity: 20 });
    expect(allocation.splits[1]).toEqual({ lot: 'LOT-B', quantity: 20 });
  });
});

describe('FEFO分配(先到期先出)', () => {
  it('首先分配到期最早的批次', async () => {
    await wms.receiveInventory('MAT-FEFO', 50, { lot: 'LOT-EXP-LATE', expiry: '2027-12-31' });
    await wms.receiveInventory('MAT-FEFO', 50, { lot: 'LOT-EXP-SOON', expiry: '2026-06-30' });

    const allocation = await wms.allocate({
      item: 'MAT-FEFO',
      quantity: 30,
      method: 'FEFO'
    });

    expect(allocation.allocatedLot).toBe('LOT-EXP-SOON');
  });

  it('从分配中排除过期批次', async () => {
    await wms.receiveInventory('MAT-EXPIRED', 100, { lot: 'LOT-PAST', expiry: '2025-12-31' });
    await wms.receiveInventory('MAT-EXPIRED', 50, { lot: 'LOT-VALID', expiry: '2027-12-31' });

    const allocation = await wms.allocate({
      item: 'MAT-EXPIRED',
      quantity: 30,
      method: 'FEFO'
    });

    expect(allocation.allocatedLot).toBe('LOT-VALID');
    expect(allocation.excludedLots).toContain('LOT-PAST');
    expect(allocation.excludeReason).toBe('EXPIRED');
  });
});

包装和发货

describe('包装验证', () => {
  it('验证包装内容匹配拣货清单', async () => {
    const packResult = await wms.packVerify({
      orderId: 'SO-1001',
      cartonId: 'CTN-001',
      scannedItems: [
        { barcode: 'MAT-100-LOT001', qty: 10 },
        { barcode: 'MAT-200-LOT002', qty: 5 }
      ]
    });

    expect(packResult.status).toBe('VERIFIED');
    expect(packResult.allItemsScanned).toBe(true);
    expect(packResult.weightValidation).toBe('WITHIN_TOLERANCE');
  });

  it('拒绝缺少物品的包装', async () => {
    const packResult = await wms.packVerify({
      orderId: 'SO-1001',
      cartonId: 'CTN-002',
      scannedItems: [
        { barcode: 'MAT-100-LOT001', qty: 10 }
        // 缺少 MAT-200
      ]
    });

    expect(packResult.status).toBe('INCOMPLETE');
    expect(packResult.missingItems).toContainEqual({ item: 'MAT-200', expectedQty: 5 });
  });
});

describe('发货和承运商分配', () => {
  it('跨承运商比价并选择最便宜的', async () => {
    const shipment = await wms.shipConfirm({
      orderId: 'SO-1001',
      cartons: ['CTN-001'],
      carrierSelection: 'RATE_SHOP',
      eligibleCarriers: ['UPS', 'FEDEX', 'USPS']
    });

    expect(shipment.status).toBe('SHIPPED');
    expect(shipment.carrier).toBeDefined();
    expect(shipment.trackingNumber).toBeDefined();
    expect(shipment.rateShopResults).toHaveLength(3);
    expect(shipment.selectedRate).toBeLessThanOrEqual(
      Math.min(...shipment.rateShopResults.map(r => r.rate))
    );
  });

  it('生成发货标签并触发EDI 856', async () => {
    const shipment = await wms.shipConfirm({
      orderId: 'SO-1002',
      cartons: ['CTN-003'],
      carrier: 'UPS',
      service: 'GROUND'
    });

    expect(shipment.label).toBeDefined();
    expect(shipment.label.format).toBe('ZPL');
    expect(shipment.edi856Generated).toBe(true);
    expect(shipment.edi856.transactionSetId).toMatch(/^856/);
  });
});

EDI文档测试

EDI 856 - 发货通知

describe('EDI 856 ASN生成', () => {
  it('生成带正确层级结构的有效856', async () => {
    const asn = await edi.generate856({
      shipmentId: 'SHP-001',
      carrier: { scac: 'UPSN', proNumber: 'PRO-12345' },
      orders: [{
        orderId: 'SO-1001',
        items: [
          { sku: 'MAT-100', qty: 10, lot: 'LOT-001', upc: '012345678901' }
        ]
      }]
    });

    // 验证层级结构:发货 -> 订单 -> 物品
    expect(asn.segments.filter(s => s.id === 'HL')).toHaveLength(3);
    expect(asn.getSegment('BSN', 'BSN01')).toBe('00'); // 原始
    expect(asn.getSegment('TD5', 'TD504')).toBe('UPSN');
    expect(asn.getSegment('SN1', 'SN102')).toBe('10');

    // 验证段数和控制号
    expect(asn.getSegment('CTT', 'CTT01')).toBe('1'); // 1个事务
    expect(asn.getSegment('SE', 'SE01')).toBeDefined(); // 段数
  });

  it('验证已发送856的997功能确认', async () => {
    const asn = await edi.send856('SHP-001');
    const ack = await edi.receive997({ timeout: 30000 });

    expect(ack.transactionSetId).toBe('997');
    expect(ack.acknowledgmentCode).toBe('A'); // 接受
    expect(ack.referencedTransactionSet).toBe('856');
  });

  it('处理997拒绝并触发重发', async () => {
    const asn = await edi.send856('SHP-INVALID');
    const ack = await edi.receive997({ timeout: 30000 });

    expect(ack.acknowledgmentCode).toBe('R'); // 拒绝
    expect(ack.errors).toContainEqual(
      expect.objectContaining({ segmentId: 'SN1', errorCode: '7' })
    );

    // 验证重发已触发
    const resendLog = await edi.getResendLog('SHP-INVALID');
    expect(resendLog.attempts).toBeGreaterThanOrEqual(1);
  });
});

EDI 940 - 仓库发货订单

describe('EDI 940 入库处理', () => {
  it('从有效940文档创建WMS订单', async () => {
    const edi940 = loadEdiDocument('testdata/edi-940-standard.edi');
    const result = await edi.process940(edi940);

    expect(result.orderCreated).toBe(true);
    expect(result.wmsOrderId).toBeDefined();
    expect(result.lineCount).toBe(5);
    expect(result.allocationTriggered).toBe(true);
    expect(result.priorityMapped).toBe('STANDARD');
  });

  it('拒绝带未知SKU的940并生成997拒绝', async () => {
    const edi940 = loadEdiDocument('testdata/edi-940-invalid-sku.edi');
    const result = await edi.process940(edi940);

    expect(result.orderCreated).toBe(false);
    expect(result.errors).toContainEqual(
      expect.objectContaining({ segment: 'LIN', error: '未知 SKU: INVALID-SKU' })
    );
    expect(result.ack997.code).toBe('R');
  });

  it('通过BSN参考号检测重复940', async () => {
    const edi940 = loadEdiDocument('testdata/edi-940-standard.edi');

    // 首次处理 - 成功
    await edi.process940(edi940);

    // 处理重复 - 应检测
    const result = await edi.process940(edi940);

    expect(result.orderCreated).toBe(false);
    expect(result.duplicate).toBe(true);
    expect(result.originalOrderId).toBeDefined();
  });
});

EDI 945 - 仓库发货建议

describe('EDI 945 出库生成', () => {
  it('发货确认时生成945', async () => {
    await wms.shipConfirm({ orderId: 'SO-1005', carrier: 'FEDEX' });

    const edi945 = await edi.getLatest945('SO-1005');

    expect(edi945.transactionSetId).toBe('945');
    expect(edi945.getSegment('W06', 'W0601')).toBe('N'); // 正常发货
    expect(edi945.getSegment('W12', 'W1202')).toBeDefined(); // 发货数量
    expect(edi945.getSegment('N1', 'N102')).toBeDefined(); // 收货方名称
  });
});

RF/条码扫描流程测试

describe('RF定向拣货流程', () => {
  let rfSession;

  beforeEach(async () => {
    rfSession = await rfSimulator.createSession({
      device: 'Zebra-MC9300',
      mode: 'directed-pick'
    });
  });

  it('用有效扫描完成定向拣货', async () => {
    // 步骤1:登录
    const login = await rfSession.scan('badge', 'USER-PICKER-01');
    expect(login.screen).toBe('MAIN_MENU');

    // 步骤2:选择拣货任务
    const taskSelect = await rfSession.selectMenu('PICK');
    expect(taskSelect.screen).toBe('PICK_TASK_ASSIGNED');
    expect(taskSelect.taskId).toBeDefined();

    // 步骤3:扫描源位置
    const locationScan = await rfSession.scan('location', 'BIN-A-01-01');
    expect(locationScan.screen).toBe('SCAN_ITEM');
    expect(locationScan.expectedItem).toBe('MAT-100');

    // 步骤4:扫描物品条码
    const itemScan = await rfSession.scan('item', 'MAT-100-LOT001');
    expect(itemScan.screen).toBe('ENTER_QUANTITY');
    expect(itemScan.maxQty).toBe(10);

    // 步骤5:输入数量
    const qtyConfirm = await rfSession.enterQuantity(10);
    expect(qtyConfirm.screen).toBe('SCAN_DESTINATION');

    // 步骤6:扫描暂存通道
    const destScan = await rfSession.scan('location', 'STAGE-LANE-01');
    expect(destScan.screen).toBe('PICK_COMPLETE');
    expect(destScan.taskStatus).toBe('COMPLETED');
  });

  it('用不匹配警报拒绝错误物品扫描', async () => {
    await rfSession.scan('badge', 'USER-PICKER-01');
    await rfSession.selectMenu('PICK');
    await rfSession.scan('location', 'BIN-A-01-01');

    const wrongItem = await rfSession.scan('item', 'MAT-999-WRONG');

    expect(wrongItem.screen).toBe('ITEM_MISMATCH');
    expect(wrongItem.alert).toBe('MISMATCH');
    expect(wrongItem.expectedItem).toBe('MAT-100');
    expect(wrongItem.scannedItem).toBe('MAT-999');
  });

  it('超拣数量时警告', async () => {
    await rfSession.scan('badge', 'USER-PICKER-01');
    await rfSession.selectMenu('PICK');
    await rfSession.scan('location', 'BIN-A-01-01');
    await rfSession.scan('item', 'MAT-100-LOT001');

    const overPick = await rfSession.enterQuantity(999);

    expect(overPick.screen).toBe('OVER_PICK_WARNING');
    expect(overPick.maxAllowed).toBe(10);
    expect(overPick.requiresSupervisor).toBe(true);
  });

  it('优雅处理错误位置扫描', async () => {
    await rfSession.scan('badge', 'USER-PICKER-01');
    await rfSession.selectMenu('PICK');

    const wrongLocation = await rfSession.scan('location', 'BIN-Z-99-99');

    expect(wrongLocation.screen).toBe('WRONG_LOCATION');
    expect(wrongLocation.expectedLocation).toBe('BIN-A-01-01');
    expect(wrongLocation.action).toBe('RESCAN');
  });
});

describe('RF收货流程', () => {
  it('通过扫描PO和物品收货', async () => {
    const rfSession = await rfSimulator.createSession({ mode: 'receiving' });

    await rfSession.scan('badge', 'USER-RECEIVER-01');
    const poScan = await rfSession.scan('document', 'PO-4500001234');
    expect(poScan.screen).toBe('SCAN_ITEM');
    expect(poScan.expectedItems).toHaveLength(3);

    const itemScan = await rfSession.scan('item', 'MAT-100');
    expect(itemScan.screen).toBe('ENTER_QUANTITY');

    const receipt = await rfSession.enterQuantity(100);
    expect(receipt.screen).toBe('SCAN_LOT');

    const lotScan = await rfSession.scan('lot', 'LOT-2026-050');
    expect(lotScan.screen).toBe('RECEIPT_CONFIRMED');
    expect(lotScan.quantityReceived).toBe(100);
  });
});

WMS-ERP集成测试

SAP EWM 到 S/4HANA 同步

describe('WMS-ERP货物收货同步', () => {
  it('在SLA内同步货物收货从EWM到S/4HANA', async () => {
    const startTime = Date.now();

    // 在EWM中确认入库交货
    await ewm.confirmInboundDelivery({
      deliveryId: 'IBD-001',
      items: [{ material: 'MAT-100', quantity: 100, batch: 'BATCH-001' }]
    });

    // 等待同步到S/4HANA
    const materialDoc = await s4hana.waitForMaterialDocument({
      reference: 'IBD-001',
      timeout: 30000
    });

    const elapsed = Date.now() - startTime;

    expect(materialDoc.created).toBe(true);
    expect(materialDoc.type).toBe('WE'); // 货物收货
    expect(materialDoc.quantity).toBe(100);
    expect(materialDoc.batch).toBe('BATCH-001');
    expect(materialDoc.plant).toBe('1000');
    expect(elapsed).toBeLessThan(30000); // 30s SLA
  });

  it('验证EWM和S/4HANA之间的库存数量匹配', async () => {
    const ewmStock = await ewm.getStock('MAT-100', 'WH-01');
    const s4Stock = await s4hana.getStock('MAT-100', '1000', '0001');

    expect(ewmStock.available).toBe(s4Stock.unrestricted);
    expect(ewmStock.allocated).toBe(s4Stock.reserved);
    expect(ewmStock.blocked).toBe(s4Stock.qualityInspection);
  });

  it('处理同步失败,带IDoc错误和重试', async () => {
    // 模拟S/4HANA不可用
    await s4hana.simulateDowntime(10000);

    await ewm.confirmInboundDelivery({
      deliveryId: 'IBD-FAIL',
      items: [{ material: 'MAT-200', quantity: 50 }]
    });

    // 检查IDoc状态
    const idoc = await sap.getIdoc({ reference: 'IBD-FAIL', type: 'WMMBID01' });
    expect(idoc.status).toBe('03'); // 错误状态

    // 恢复S/4HANA并验证自动重试
    await s4hana.restoreService();
    const retried = await sap.waitForIdocStatus(idoc.id, '53', { timeout: 60000 });
    expect(retried.status).toBe('53'); // 成功处理
  });
});

Manhattan WMOS 到 Oracle EBS 同步

describe('Manhattan WMOS 到 Oracle EBS', () => {
  it('通过REST API向Oracle发布库存调整', async () => {
    const adjustment = await wmos.postAdjustment({
      item: 'ITEM-500',
      location: 'LOC-A-01',
      adjustmentQty: -5,
      reason: 'DAMAGED'
    });

    const oracleTransaction = await oracle.waitForTransaction({
      reference: adjustment.transactionId,
      timeout: 15000
    });

    expect(oracleTransaction.transactionType).toBe('ADJUSTMENT');
    expect(oracleTransaction.quantity).toBe(-5);
    expect(oracleTransaction.reasonCode).toBe('DAMAGED');
    expect(oracleTransaction.accountPosted).toBe(true);
  });
});

退货和逆向物流

describe('退货处理', () => {
  it('处理RMA收货,带检查和处置', async () => {
    const rma = await wms.createRMA({
      rmaNumber: 'RMA-001',
      originalOrder: 'SO-1001',
      items: [{ sku: 'MAT-100', qty: 2, reason: 'DEFECTIVE' }]
    });

    // 接收退货
    const receipt = await wms.receiveReturn({
      rmaId: rma.id,
      scannedItems: [{ barcode: 'MAT-100', qty: 2 }]
    });

    expect(receipt.status).toBe('RECEIVED_PENDING_INSPECTION');

    // 检查退货物品
    const inspection = await wms.inspectReturn({
      rmaId: rma.id,
      results: [
        { item: 'MAT-100', unit: 1, condition: 'RESTOCK', grade: 'A' },
        { item: 'MAT-100', unit: 2, condition: 'SCRAP', grade: 'F' }
      ]
    });

    expect(inspection.disposition.restock).toBe(1);
    expect(inspection.disposition.scrap).toBe(1);
    expect(inspection.restockLocation).toBeDefined();
  });
});

补货测试

describe('补货触发器', () => {
  it('当拣选面低于最小值时触发最小/最大补货', async () => {
    // 设置拣选面到最小阈值
    await wms.setInventory('PICK-A-01', 'MAT-100', 5); // 最小 = 10, 最大 = 50

    const replenishment = await wms.checkReplenishment('PICK-A-01', 'MAT-100');

    expect(replenishment.triggered).toBe(true);
    expect(replenishment.type).toBe('MIN_MAX');
    expect(replenishment.replenishQty).toBe(45); // 填充到最大 (50 - 5)
    expect(replenishment.sourceLocation).toMatch(/^BULK-/);
  });

  it('从波次分配触发基于需求的补货', async () => {
    // 波次需要100 EA,拣选面仅有20
    await wms.setInventory('PICK-B-01', 'MAT-200', 20);

    const wave = await wms.planWave({
      strategy: 'SINGLE_ORDER',
      orders: [{ orderId: 'SO-REPLEN', lines: [{ item: 'MAT-200', qty: 100 }] }]
    });

    const replenishment = await wms.getReplenishmentTasks(wave.id);

    expect(replenishment).toHaveLength(1);
    expect(replenishment[0].type).toBe('DEMAND');
    expect(replenishment[0].quantity).toBe(80); // 100 需要 - 20 可用
    expect(replenishment[0].priority).toBe('URGENT');
  });
});

最佳实践

要这样做

  • 测试完整生命周期:收货 -> 上架 -> 分配 -> 拣货 -> 包装 -> 发货 -> EDI
  • 在每个状态转换时验证库存准确性(在手、已分配、在途)
  • 测试所有WMS分配方法(FIFO、FEFO、LIFO、区优先级)
  • 模拟RF设备故障在事务中(电池耗尽、WiFi中断、扫描超时)
  • 验证每个出库文档的EDI确认(997)
  • 显式测试短拣、超拣和错误拣货场景
  • 验证WMS-ERP同步延迟相对于SLA阈值
  • 测试多个阈值的周期盘点差异(自动批准、主管、重新盘点)

避免这样

  • 仅测试快乐路径收货,无超/欠容差场景
  • 跳过RF错误流程(错误扫描、超时、连接丢失)
  • 忽略EDI段级验证(仅信任文档级通过)
  • 测试分配逻辑无过期批次场景
  • 假设WMS-ERP同步是瞬时的;始终用定时断言测试
  • 部署前未测试混合优先级订单的波次计划
  • 忽略包装验证中的纸箱化和重量验证

代理辅助WMS测试

// 验证EDI文档合同
await Task("EDI合同验证", {
  documents: ['856-ASN', '940-Order', '945-Advice', '943-StockTransfer'],
  validateSegments: true,
  checkAcknowledgments: true,
  testErrorScenarios: ['invalid-sku', 'duplicate-bsn', 'missing-required']
}, "qe-contract-validator");

// 测试WMS-ERP集成流程
await Task("WMS-ERP集成测试", {
  source: 'SAP-EWM',
  target: 'SAP-S4HANA',
  flows: ['goods-receipt', 'goods-issue', 'stock-transfer', 'inventory-adjustment'],
  slaThreshold: 30000,
  validateIdocs: true
}, "qe-middleware-validator");

// 验证SAP IDoc结构
await Task("IDoc结构验证", {
  idocTypes: ['WMMBID01', 'SHPCON', 'MBGMCR01'],
  validateSegments: true,
  testMandatoryFields: true,
  crossReferenceWithEWM: true
}, "qe-sap-idoc-tester");

// 测试EWM OData服务
await Task("EWM OData服务测试", {
  services: [
    '/sap/opu/odata/sap/API_WAREHOUSE_ORDER',
    '/sap/opu/odata/sap/API_PHYSICAL_INVENTORY_DOC'
  ],
  testCrud: true,
  validateNavigationProperties: true,
  testFilterAndPagination: true
}, "qe-odata-contract-validator");

代理协调提示

主要代理

  • qe-middleware-validator:WMS-ERP集成流程测试和消息转换验证
  • qe-contract-validator:EDI文档合同测试和WMS API合同

支持代理

  • qe-message-broker-tester:当WMS使用消息队列进行异步处理时
  • qe-odata-contract-validator:当WMS暴露OData服务时(SAP EWM)
  • qe-sap-idoc-tester:当WMS-SAP集成使用IDocs时(WMMBID01、SHPCON)

协调模式

1. qe-contract-validator -> 验证EDI文档模式和段
2. qe-middleware-validator -> 测试WMS消息流程和转换
3. qe-message-broker-tester -> 验证异步队列处理和DLQ
4. qe-odata-contract-validator -> 测试WMS OData API(如果SAP EWM)
5. qe-sap-idoc-tester -> 验证IDoc结构和内容

内存命名空间

aqe/wms-testing/
  inventory/         - 收货、上架、调整测试结果
  fulfillment/       - 拣货/包装/发货流程结果
  edi/               - EDI文档验证结果
  rf-scanning/       - RF流程模拟结果
  allocation/        - FIFO/FEFO/批次控制测试结果
  integration/       - WMS-ERP同步测试结果
  returns/           - RMA和逆向物流结果

车队协调

const wmsFleet = await FleetManager.coordinate({
  strategy: 'wms-testing',
  agents: [
    'qe-contract-validator',     // EDI合同和API合同
    'qe-middleware-validator',    // WMS-ERP消息流程
    'qe-sap-idoc-tester',        // IDoc验证
    'qe-odata-contract-validator'   // SAP EWM OData测试
  ],
  topology: 'mesh'
});

await wmsFleet.execute({
  workflows: [
    { name: 'inbound', stages: ['receive', 'putaway', 'inventory-sync'] },
    { name: 'outbound', stages: ['wave', 'pick', 'pack', 'ship', 'edi-856'] },
    { name: 'returns', stages: ['rma-receipt', 'inspection', 'disposition'] }
  ],
  testEdi: true,
  testRfScanning: true,
  testAllocation: true
});

QCSD集成

  • 构思:标记 HAS_WMS 触发WMS特定质量标准(库存准确性、EDI合规性)
  • 精化:SFDIPOT 包括仓库操作产品因素(吞吐量、准确性、延迟)
  • 开发:覆盖分析包括EDI文档解析路径和分配算法
  • 验证:流水线门包括WMS集成健康检查和EDI确认验证

相关技能


记住

仓库管理系统是物理数字桥梁。当库存错误时,客户收到错误发货,财务报告不准确,补货失败。测试每个库存状态转换、每个EDI文档字段和每个RF扫描异常。2%的库存差异看似小,但在50个仓库的100,000个SKU中会累积放大。

使用代理: 代理段对段验证EDI文档结构,在SLA阈值内测试WMS-ERP同步,并大规模模拟RF扫描流程。使用代理覆盖分配方法、批次控制场景和EDI错误条件的组合爆炸,这是手动测试无法达到的。