qe-wms-testing-patterns について
qe-wms-testing-patternsは、倉庫管理システム(WMS)のテストパターン集です。テスターが在庫、出荷業務、EDI、システム連携を検証するためのシナリオや例外ケース、エージェント連携の手順をまとめています。
主な機能
- 01入荷、格納、棚卸し、在庫調整の検証
- 02波動ピッキング、梱包、出荷のテスト
- 03EDI文書と業務ルールの検証
- 04RFおよびバーコードスキャンの確認
- 05引当、補充、WMS-ERP同期の検証
活用シーン
- 01在庫差異と調整ロジックの調査
- 02波動リリースから出荷までの注文処理をテスト
- 03EDI文書と確認メッセージの検証
- 04スキャン障害や連携時の例外をシミュレート
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ proffesor-for-testing/ agentic-qe --skill qe-wms-testing-patternsインストール後、Claude Code で/qe-wms-testing-patternsを入力して読み込まれたか確認します。
詳細
WMS テストパターン
<default_to_action> 倉庫管理システムをテストする場合:
- 在庫の正確性を検証します(入荷、格納、循環棚卸、調整)
- ピッキング/梱包/出荷のワークフローをエンドツーエンドでテストします(ウェーブのリリース → 出荷確定)
- EDI メッセージの処理を検証します(856 ASN、940 注文、945 確認、943/944 在庫)
- RF/バーコードスキャンのフローを検証します(スキャン → 検証 → 更新 → 確認)
- 引当と補充のロジックを実行します(FIFO、FEFO、ロット管理)
- WMS と ERP の連携をテストします(在庫同期、注文ステータス、入荷)
- ウェーブ管理を検証します(ウェーブ計画、リリース、ショートピックへの対応)
クイックパターン選択:
- 在庫差異 → 循環棚卸と在庫調整のテスト
- 注文処理の問題 → ピッキング/梱包/出荷ワークフローのテスト
- 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 の在庫同期 | フルスタック | 低速 |
| E2E ワークフロー | 注文処理サイクル全体 | WMS + ERP + TMS | 低速 |
重要なテストシナリオ
| シナリオ | 必須テスト項目 | 例 |
|---|---|---|
| 入荷 | 入荷精度 | 過剰/不足納品の許容範囲を含む発注書の入荷 |
| 格納 | 保管場所の割り当て | 商品属性に基づく指示格納 |
| ピッキング | ウェーブ/バッチピッキング | 優先順位に基づく引当を含む複数注文のウェーブ |
| 梱包 | 梱包検証 | カートン化と重量/寸法の検証 |
| 出荷 | 配送業者の割り当て | 運賃比較とラベル生成 |
| 循環棚卸 | 在庫精度 | ブラインドカウントとガイド付きカウントの照合 |
| 返品 | 返品物流 | 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-tester:SAP EWM OData サービスのテスト
在庫トランザクションのテスト
入荷と格納
describe('Inbound Receipt - PO with Lot Control', () => {
it('receives goods against PO with lot tracking', 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');
// Verify putaway task was created
const putawayTask = await wms.getTask(receipt.putawayTaskId);
expect(putawayTask.type).toBe('DIRECTED_PUTAWAY');
expect(putawayTask.suggestedLocation).toMatch(/^BIN-/);
});
it('rejects over-receipt beyond tolerance', async () => {
// PO line is for 100 EA, tolerance is 10%
const result = await wms.receive({
po: 'PO-4500001234',
item: 'MAT-100',
quantity: 115, // 15% over, exceeds 10% tolerance
lot: 'LOT-2026-002'
});
expect(result.status).toBe('REJECTED');
expect(result.reason).toContain('Over-receipt tolerance exceeded');
expect(result.maxAllowed).toBe(110);
});
it('accepts under-receipt and creates remaining open quantity', 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('Directed Putaway', () => {
it('assigns location based on product attributes', async () => {
// Hazmat product should go to hazmat zone
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-/); // Hazmat zone prefix
expect(putaway.status).toBe('PUTAWAY_COMPLETE');
});
it('falls back to overflow when primary zone is full', async () => {
// Fill the primary zone first
await wms.fillZone('ZONE-A', 100); // 100% capacity
const putaway = await wms.executePutaway({
item: 'MAT-200',
quantity: 10,
preferredZone: 'ZONE-A'
});
expect(putaway.location).toMatch(/^OVF-/); // Overflow zone
expect(putaway.overflowReason).toBe('PRIMARY_ZONE_FULL');
});
});
循環棚卸と調整
describe('Cycle Count - Blind Count', () => {
it('auto-approves variance within threshold', async () => {
// System shows 100 EA, physical count is 98
const count = await wms.submitCycleCount({
location: 'BIN-A-01-01',
item: 'MAT-100',
countedQuantity: 98,
countType: 'BLIND', // Counter does not see system quantity
varianceThreshold: 5 // percent
});
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('requires supervisor approval when variance exceeds threshold', async () => {
const count = await wms.submitCycleCount({
location: 'BIN-A-01-02',
item: 'MAT-200',
countedQuantity: 80,
countType: 'BLIND',
varianceThreshold: 5 // 20% variance exceeds 5% threshold
});
expect(count.variancePercent).toBeCloseTo(20.0);
expect(count.autoApproved).toBe(false);
expect(count.adjustmentPosted).toBe(false);
expect(count.status).toBe('PENDING_SUPERVISOR_APPROVAL');
});
it('triggers recount when variance is critical', async () => {
const count = await wms.submitCycleCount({
location: 'BIN-A-01-03',
item: 'MAT-300',
countedQuantity: 0, // System shows 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('Wave Planning and Release', () => {
it('creates wave from eligible orders with batch-pick strategy', async () => {
// Create 5 orders for wave planning
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');
// Rush order should be first in pick sequence
const firstTask = wave.pickTasks[0];
expect(firstTask.orderId).toBe('SO-1000');
expect(firstTask.priority).toBe('RUSH');
});
it('handles short-pick by creating backorder', async () => {
// Only 5 EA available, order needs 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);
// Attempt to pick
const pickResult = await wms.confirmPick({
taskId: wave.pickTasks[0].id,
pickedQty: 5 // Only 5 available
});
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 Allocation', () => {
it('allocates oldest lot first', async () => {
// Set up two lots with different receipt dates
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('splits allocation across lots when oldest is insufficient', 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 Allocation (First Expired, First Out)', () => {
it('allocates lot with earliest expiry first', 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('excludes expired lots from allocation', 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('Pack Verification', () => {
it('verifies pack contents match pick list', 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('rejects pack with missing items', async () => {
const packResult = await wms.packVerify({
orderId: 'SO-1001',
cartonId: 'CTN-002',
scannedItems: [
{ barcode: 'MAT-100-LOT001', qty: 10 }
// Missing MAT-200
]
});
expect(packResult.status).toBe('INCOMPLETE');
expect(packResult.missingItems).toContainEqual({ item: 'MAT-200', expectedQty: 5 });
});
});
describe('Shipping and Carrier Assignment', () => {
it('rate-shops across carriers and selects cheapest', 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('generates shipping label and triggers 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 Generation', () => {
it('generates valid 856 with correct hierarchical levels', 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' }
]
}]
});
// Validate hierarchical structure: Shipment -> Order -> Item
expect(asn.segments.filter(s => s.id === 'HL')).toHaveLength(3);
expect(asn.getSegment('BSN', 'BSN01')).toBe('00'); // Original
expect(asn.getSegment('TD5', 'TD504')).toBe('UPSN');
expect(asn.getSegment('SN1', 'SN102')).toBe('10');
// Validate segment count and control numbers
expect(asn.getSegment('CTT', 'CTT01')).toBe('1'); // 1 transaction
expect(asn.getSegment('SE', 'SE01')).toBeDefined(); // Segment count
});
it('validates 997 functional acknowledgment for sent 856', 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'); // Accepted
expect(ack.referencedTransactionSet).toBe('856');
});
it('handles 997 rejection and triggers resend', async () => {
const asn = await edi.send856('SHP-INVALID');
const ack = await edi.receive997({ timeout: 30000 });
expect(ack.acknowledgmentCode).toBe('R'); // Rejected
expect(ack.errors).toContainEqual(
expect.objectContaining({ segmentId: 'SN1', errorCode: '7' })
);
// Verify resend was triggered
const resendLog = await edi.getResendLog('SHP-INVALID');
expect(resendLog.attempts).toBeGreaterThanOrEqual(1);
});
});
EDI 940 - 倉庫出荷指示
describe('EDI 940 Inbound Processing', () => {
it('creates WMS order from valid 940 document', 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('rejects 940 with unknown SKU and generates 997 rejection', 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: 'Unknown SKU: INVALID-SKU' })
);
expect(result.ack997.code).toBe('R');
});
it('detects duplicate 940 by BSN reference number', async () => {
const edi940 = loadEdiDocument('testdata/edi-940-standard.edi');
// Process first time - success
await edi.process940(edi940);
// Process duplicate - should detect
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 Outbound Generation', () => {
it('generates 945 on shipment confirmation', 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'); // Normal shipment
expect(edi945.getSegment('W12', 'W1202')).toBeDefined(); // Quantity shipped
expect(edi945.getSegment('N1', 'N102')).toBeDefined(); // Ship-to name
});
});
RF/バーコードスキャンフローのテスト
describe('RF Directed Pick Workflow', () => {
let rfSession;
beforeEach(async () => {
rfSession = await rfSimulator.createSession({
device: 'Zebra-MC9300',
mode: 'directed-pick'
});
});
it('completes directed pick with valid scans', async () => {
// Step 1: Login
const login = await rfSession.scan('badge', 'USER-PICKER-01');
expect(login.screen).toBe('MAIN_MENU');
// Step 2: Select pick task
const taskSelect = await rfSession.selectMenu('PICK');
expect(taskSelect.screen).toBe('PICK_TASK_ASSIGNED');
expect(taskSelect.taskId).toBeDefined();
// Step 3: Scan source location
const locationScan = await rfSession.scan('location', 'BIN-A-01-01');
expect(locationScan.screen).toBe('SCAN_ITEM');
expect(locationScan.expectedItem).toBe('MAT-100');
// Step 4: Scan item barcode
const itemScan = await rfSession.scan('item', 'MAT-100-LOT001');
expect(itemScan.screen).toBe('ENTER_QUANTITY');
expect(itemScan.maxQty).toBe(10);
// Step 5: Enter quantity
const qtyConfirm = await rfSession.enterQuantity(10);
expect(qtyConfirm.screen).toBe('SCAN_DESTINATION');
// Step 6: Scan staging lane
const destScan = await rfSession.scan('location', 'STAGE-LANE-01');
expect(destScan.screen).toBe('PICK_COMPLETE');
expect(destScan.taskStatus).toBe('COMPLETED');
});
it('rejects wrong item scan with mismatch alert', 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('warns on over-pick quantity', 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('handles wrong location scan gracefully', 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 Receiving Workflow', () => {
it('receives goods by scanning PO and items', 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 Goods Receipt Sync', () => {
it('syncs goods receipt from EWM to S/4HANA within SLA', async () => {
const startTime = Date.now();
// Confirm inbound delivery in EWM
await ewm.confirmInboundDelivery({
deliveryId: 'IBD-001',
items: [{ material: 'MAT-100', quantity: 100, batch: 'BATCH-001' }]
});
// Wait for sync to 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'); // Goods receipt
expect(materialDoc.quantity).toBe(100);
expect(materialDoc.batch).toBe('BATCH-001');
expect(materialDoc.plant).toBe('1000');
expect(elapsed).toBeLessThan(30000); // 30s SLA
});
it('validates inventory quantities match between EWM and 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('handles sync failure with IDoc error and retry', async () => {
// Simulate S/4HANA unavailability
await s4hana.simulateDowntime(10000);
await ewm.confirmInboundDelivery({
deliveryId: 'IBD-FAIL',
items: [{ material: 'MAT-200', quantity: 50 }]
});
// Check IDoc status
const idoc = await sap.getIdoc({ reference: 'IBD-FAIL', type: 'WMMBID01' });
expect(idoc.status).toBe('03'); // Error status
// Restore S/4HANA and verify auto-retry
await s4hana.restoreService();
const retried = await sap.waitForIdocStatus(idoc.id, '53', { timeout: 60000 });
expect(retried.status).toBe('53'); // Successfully processed
});
});
Manhattan WMOS から Oracle EBS への同期
describe('Manhattan WMOS to Oracle EBS', () => {
it('posts inventory adjustment to Oracle via REST API', 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('Returns Processing', () => {
it('processes RMA receipt with inspection and disposition', async () => {
const rma = await wms.createRMA({
rmaNumber: 'RMA-001',
originalOrder: 'SO-1001',
items: [{ sku: 'MAT-100', qty: 2, reason: 'DEFECTIVE' }]
});
// Receive return
const receipt = await wms.receiveReturn({
rmaId: rma.id,
scannedItems: [{ barcode: 'MAT-100', qty: 2 }]
});
expect(receipt.status).toBe('RECEIVED_PENDING_INSPECTION');
// Inspect returned items
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('Replenishment Triggers', () => {
it('triggers min/max replenishment when pick face drops below min', async () => {
// Set pick face to minimum threshold
await wms.setInventory('PICK-A-01', 'MAT-100', 5); // Min = 10, Max = 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); // Fill to max (50 - 5)
expect(replenishment.sourceLocation).toMatch(/^BULK-/);
});
it('triggers demand-based replenishment from wave allocation', async () => {
// Wave needs 100 EA, pick face only has 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 needed - 20 available
expect(replenishment[0].priority).toBe('URGENT');
});
});
ベストプラクティス
実施すること
- 完全なライフサイクルをテストします:入荷 -> 棚入れ -> 引き当て -> ピッキング -> 梱包 -> 出荷 -> EDI
- 各状態遷移時に在庫の正確性(手持ち、引き当て済み、輸送中)を検証します
- WMS のすべての引き当て方法(FIFO、FEFO、LIFO、ゾーン優先度)をテストします
- トランザクションの途中で発生する RF デバイスの障害(バッテリー切れ、WiFi 切断、スキャンタイムアウト)をシミュレートします
- すべての送信ドキュメントについて EDI 確認応答(997)を検証します
- ピッキング不足、過剰ピッキング、誤ピッキングのシナリオを明示的にテストします
- WMS-ERP の同期遅延が SLA のしきい値内であることを検証します
- 複数のしきい値(自動承認、スーパーバイザー確認、再カウント)で棚卸差異をテストします
避けること
- 許容範囲を超える/下回るシナリオをテストせず、正常な入荷フローだけをテストすること
- RF のエラーフロー(誤スキャン、タイムアウト、接続喪失)を省略すること
- EDI のセグメントレベルの検証を無視すること(ドキュメントレベルの合格を鵜呑みにすること)
- 期限切れロットのシナリオをテストせずに引き当てロジックをテストすること
- WMS-ERP の同期が瞬時に行われると想定すること。必ずタイミングのアサーションを使ってテストしてください
- 優先度が混在する注文でのウェーブ計画をテストせずにデプロイすること
- 梱包検証でのカートン化と重量検証を無視すること
エージェント支援による WMS テスト
// Validate EDI document contracts
await Task("EDI Contract Validation", {
documents: ['856-ASN', '940-Order', '945-Advice', '943-StockTransfer'],
validateSegments: true,
checkAcknowledgments: true,
testErrorScenarios: ['invalid-sku', 'duplicate-bsn', 'missing-required']
}, "qe-contract-validator");
// Test WMS-ERP integration flows
await Task("WMS-ERP Integration Test", {
source: 'SAP-EWM',
target: 'SAP-S4HANA',
flows: ['goods-receipt', 'goods-issue', 'stock-transfer', 'inventory-adjustment'],
slaThreshold: 30000,
validateIdocs: true
}, "qe-middleware-validator");
// Validate SAP IDoc structures
await Task("IDoc Structure Validation", {
idocTypes: ['WMMBID01', 'SHPCON', 'MBGMCR01'],
validateSegments: true,
testMandatoryFields: true,
crossReferenceWithEWM: true
}, "qe-sap-idoc-tester");
// Test EWM OData services
await Task("EWM OData Service Testing", {
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-tester");
エージェント連携のヒント
主なエージェント
- qe-middleware-validator:WMS-ERP 統合フローのテストとメッセージ変換の検証
- qe-contract-validator:EDI ドキュメントのコントラクトテストと WMS API コントラクト
支援エージェント
- qe-message-broker-tester:WMS が非同期処理にメッセージキューを使用する場合
- qe-odata-contract-tester:WMS が OData サービス(SAP EWM)を公開する場合
- qe-sap-idoc-tester:WMS-SAP 統合で IDoc(WMMBID01、SHPCON)を使用する場合
連携パターン
1. qe-contract-validator -> Validate EDI document schemas and segments
2. qe-middleware-validator -> Test WMS message flows and transformations
3. qe-message-broker-tester -> Verify async queue processing and DLQ
4. qe-odata-contract-tester -> Test WMS OData APIs (if SAP EWM)
5. qe-sap-idoc-tester -> Validate IDoc structure and content
メモリ名前空間
aqe/wms-testing/
inventory/ - Receipt, putaway, adjustment test results
fulfillment/ - Pick/pack/ship workflow results
edi/ - EDI document validation results
rf-scanning/ - RF workflow simulation results
allocation/ - FIFO/FEFO/lot control test results
integration/ - WMS-ERP sync test results
returns/ - RMA and reverse logistics results
エージェント群の連携
const wmsFleet = await FleetManager.coordinate({
strategy: 'wms-testing',
agents: [
'qe-contract-validator', // EDI contracts and API contracts
'qe-middleware-validator', // WMS-ERP message flows
'qe-sap-idoc-tester', // IDoc validation
'qe-odata-contract-tester' // SAP EWM OData testing
],
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 確認応答の検証が含まれます
関連スキル
- api-testing-patterns - REST/GraphQL API テストの基礎
- contract-testing - Pact によるコンシューマー駆動型コントラクトテスト
- enterprise-integration-testing - すべてのエンタープライズエージェントのオーケストレーション
- middleware-testing-patterns - ESB とメッセージブローカーのテスト
- observability-testing-patterns - 監視とアラートの検証
忘れないでください
倉庫管理システムは、物理世界とデジタル世界をつなぐ橋渡し役です。在庫に誤りがあると、顧客には誤った出荷が届き、財務レポートも不正確になり、補充にも失敗します。在庫のすべての状態遷移、すべての EDI ドキュメントフィールド、すべての RF スキャン例外をテストしてください。在庫差異が 2%なら小さく見えるかもしれませんが、50 の倉庫にある 100,000 SKU 全体で積み重なると、大きな問題になります。
エージェントを使用する場合: エージェントは EDI ドキュメント構造をセグメント単位で検証し、WMS-ERP の同期が SLA のしきい値内であることをテストし、大規模な RF スキャンワークフローをシミュレートします。手動テストでは網羅できない、引き当て方法、ロット管理のシナリオ、EDI エラー条件の組み合わせ爆発をカバーするために、エージェントを活用してください。
コメント
Markdown 対応