feat: 1단계 — JSON 시나리오 로더 + 스모크 테스트
- ScenarioLoader: JSON 시나리오/창고 레이아웃 → 런타임 ScriptableObject 변환 - warehouse_basic.json: 로케이션 마스터 (해피패스 + 존위반/용량초과 함정) - InboundManager.StartMission(scenario) 오버로드 (로더 결과 주입용) - Editor 스모크 테스트: 로더 + 입고 로직 12항목 검증 (12 PASS) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using SinoWms.Core;
|
||||
using SinoWms.Data;
|
||||
using SinoWms.Inbound;
|
||||
|
||||
namespace SinoWms.EditorTools
|
||||
{
|
||||
/// <summary>
|
||||
/// UI 없이 입고 로직(로더 + 서비스)을 검증하는 스모크 테스트.
|
||||
/// 메뉴 <b>SinoWMS/Run Inbound Smoke Test</b>로 실행하면 콘솔에 PASS/FAIL 리포트가 찍힌다.
|
||||
/// 정식 EditMode 테스트(asmdef 분리)로 대체되기 전까지의 임시 검증 수단.
|
||||
/// </summary>
|
||||
public static class InboundSmokeTest
|
||||
{
|
||||
private static int _pass;
|
||||
private static int _fail;
|
||||
|
||||
[MenuItem("SinoWMS/Run Inbound Smoke Test")]
|
||||
public static void Run()
|
||||
{
|
||||
_pass = 0;
|
||||
_fail = 0;
|
||||
Debug.Log("===== 입고 스모크 테스트 시작 =====");
|
||||
|
||||
var loaded = ScenarioLoader.LoadFromResources(
|
||||
"Scenarios/inbound_basic", "Scenarios/warehouse_basic");
|
||||
|
||||
TestLoader(loaded);
|
||||
TestHappyPath(loaded);
|
||||
TestUnknownBarcode(loaded);
|
||||
TestPutawayBeforeInspect(loaded);
|
||||
TestZoneMismatch(loaded);
|
||||
TestOverCapacity(loaded);
|
||||
|
||||
var summary = $"===== 결과: {_pass} PASS / {_fail} FAIL =====";
|
||||
if (_fail == 0) Debug.Log(summary);
|
||||
else Debug.LogError(summary);
|
||||
}
|
||||
|
||||
// ---- 개별 테스트 ----
|
||||
|
||||
private static void TestLoader(ScenarioLoader.LoadedScenario l)
|
||||
{
|
||||
Check("로더: 시나리오 ID", l.scenario.scenarioId == "INB-001");
|
||||
Check("로더: 라인 3건", l.scenario.lines.Count == 3);
|
||||
Check("로더: 로케이션 7건", l.locations.Count == 7);
|
||||
Check("로더: 우유 존=Chilled",
|
||||
Line(l, "SKU-2002").item.requiredZone == StorageZone.Chilled);
|
||||
Check("로더: 정답 로케이션 해석(생수→A-01-01)",
|
||||
Line(l, "SKU-1001").suggestedLocation?.locationCode == "A-01-01");
|
||||
Check("로더: 수량 차이 함정(우유 예정8/실제6)",
|
||||
Line(l, "SKU-2002").actualQty == 6 && Line(l, "SKU-2002").expectedQty == 8);
|
||||
}
|
||||
|
||||
private static void TestHappyPath(ScenarioLoader.LoadedScenario l)
|
||||
{
|
||||
var progress = NewProgress(l);
|
||||
var scan = new ScanService(progress);
|
||||
var putaway = new PutawayService();
|
||||
|
||||
var lp = progress.First(p => p.line.item.sku == "SKU-1001");
|
||||
var r1 = scan.Scan(lp.line.item.barcode, out _);
|
||||
Check("해피: 정상 스캔", r1 == ScanResult.Ok && lp.scanned);
|
||||
|
||||
lp.countedQty = lp.line.actualQty;
|
||||
lp.inspected = true;
|
||||
|
||||
var r2 = putaway.Commit(lp, Loc(l, "A-01-01"));
|
||||
Check("해피: 정상 적치", r2 == PutawayResult.Ok && lp.putaway);
|
||||
}
|
||||
|
||||
private static void TestUnknownBarcode(ScenarioLoader.LoadedScenario l)
|
||||
{
|
||||
var scan = new ScanService(NewProgress(l));
|
||||
var r = scan.Scan("0000000000000", out var matched);
|
||||
Check("함정: 미등록 바코드 → UnknownBarcode",
|
||||
r == ScanResult.UnknownBarcode && matched == null);
|
||||
}
|
||||
|
||||
private static void TestPutawayBeforeInspect(ScenarioLoader.LoadedScenario l)
|
||||
{
|
||||
var progress = NewProgress(l);
|
||||
var putaway = new PutawayService();
|
||||
var lp = progress.First(p => p.line.item.sku == "SKU-1001");
|
||||
lp.scanned = true; // 검수는 생략
|
||||
var r = putaway.Validate(lp, Loc(l, "A-01-01"));
|
||||
Check("함정: 검수 전 적치 → NotInspected", r == PutawayResult.NotInspected);
|
||||
}
|
||||
|
||||
private static void TestZoneMismatch(ScenarioLoader.LoadedScenario l)
|
||||
{
|
||||
var progress = NewProgress(l);
|
||||
var putaway = new PutawayService();
|
||||
var lp = progress.First(p => p.line.item.sku == "SKU-1001"); // Ambient 품목
|
||||
lp.countedQty = lp.line.actualQty;
|
||||
lp.inspected = true;
|
||||
var r = putaway.Validate(lp, Loc(l, "C-02-03")); // Chilled 존
|
||||
Check("함정: 존 위반 → ZoneMismatch", r == PutawayResult.ZoneMismatch);
|
||||
}
|
||||
|
||||
private static void TestOverCapacity(ScenarioLoader.LoadedScenario l)
|
||||
{
|
||||
var progress = NewProgress(l);
|
||||
var putaway = new PutawayService();
|
||||
var lp = progress.First(p => p.line.item.sku == "SKU-1001"); // 10개 × 부피2 = 20
|
||||
lp.countedQty = lp.line.actualQty;
|
||||
lp.inspected = true;
|
||||
var r = putaway.Validate(lp, Loc(l, "A-99-01")); // 용량 5
|
||||
Check("함정: 용량 초과 → OverCapacity", r == PutawayResult.OverCapacity);
|
||||
}
|
||||
|
||||
// ---- 헬퍼 ----
|
||||
|
||||
private static List<LineProgress> NewProgress(ScenarioLoader.LoadedScenario l)
|
||||
=> l.scenario.lines.Select(line => new LineProgress(line)).ToList();
|
||||
|
||||
private static InboundLine Line(ScenarioLoader.LoadedScenario l, string sku)
|
||||
=> l.scenario.lines.First(line => line.item.sku == sku);
|
||||
|
||||
private static LocationDefinition Loc(ScenarioLoader.LoadedScenario l, string code)
|
||||
=> l.locations.First(loc => loc.locationCode == code);
|
||||
|
||||
private static void Check(string label, bool ok)
|
||||
{
|
||||
if (ok) { _pass++; Debug.Log($" ✅ {label}"); }
|
||||
else { _fail++; Debug.LogError($" ❌ {label}"); }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 75a0eb77b8a87495cb9a80a685e0e1a8
|
||||
Reference in New Issue
Block a user