From 7b007bddfae7d7a70cd7316e0ec49583f03c382a Mon Sep 17 00:00:00 2001 From: hjhan Date: Thu, 23 Jul 2026 11:16:11 +0800 Subject: [PATCH] =?UTF-8?q?refactor(swap-test):=20=E6=8A=BD=E5=8F=96=20Tes?= =?UTF-8?q?tableSwapEodPositionService=20=E5=85=AC=E5=85=B1=E5=9F=BA?= =?UTF-8?q?=E7=B1=BB=E6=94=B6=E6=95=9B=E9=87=8D=E5=A4=8D=20Stub?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新建 TestableSwapEodPositionService 收敛 8/8 Stub 重复的高频 override (PersistEodSwapPosition/SaveAllChanges/GetCurrencyRate/AddClientCash) + 统一 OptUserInfo 构造 + PersistedPositions/SaveChangesCount 输出捕获 - SwapEodPositionService.DealInterests 改 protected virtual(行为零变化) - 8 个 ScenarioTest 改为继承基类,删除重复 override - 消灭 DealInterestsScenarioTest/DealInterestsGoldenReplayTest 的反射调用 (typeof().GetMethod().Invoke → 直接调用 DealInterests) 验证:dotnet build 0 错误;dotnet test SwapModule 284通过/6跳过/0失败 --- .../ClearSwapPositionsScenarioTest.cs | 4 +- .../SwapModule/ComposePageScenarioTest.cs | 29 ++------ .../DealFloatPositionsScenarioTest.cs | 9 +-- .../DealInterestsGoldenReplayTest.cs | 38 +++++------ .../SwapModule/DealInterestsScenarioTest.cs | 34 ++-------- .../SwapModule/GLMS20260105GoldenTest.cs | 7 +- .../MultiUnwindDividendConservationTest.cs | 12 +--- .../SwapPositionComposeScenarioTest.cs | 15 ++-- .../TestableSwapEodPositionService.cs | 68 +++++++++++++++++++ .../SwapModule/SwapEodPositionService.cs | 2 +- 10 files changed, 109 insertions(+), 109 deletions(-) create mode 100644 UnitTestProject/Modules/SwapModule/TestableSwapEodPositionService.cs diff --git a/UnitTestProject/Modules/SwapModule/ClearSwapPositionsScenarioTest.cs b/UnitTestProject/Modules/SwapModule/ClearSwapPositionsScenarioTest.cs index cbcc0f5f..7e1fb330 100644 --- a/UnitTestProject/Modules/SwapModule/ClearSwapPositionsScenarioTest.cs +++ b/UnitTestProject/Modules/SwapModule/ClearSwapPositionsScenarioTest.cs @@ -21,7 +21,7 @@ namespace YLErp.Modules.SwapModule #region Stub - private sealed class StubService : SwapEodPositionService + private sealed class StubService : TestableSwapEodPositionService { // 注入的内存数据 public List FlowEvents { get; set; } = new(); @@ -30,7 +30,7 @@ namespace YLErp.Modules.SwapModule public List DeletedRecords { get; } = new(); - public StubService() : base(new OptUserInfo(0, nameof(ClearSwapPositionsScenarioTest), OptUserFrom.UnitTest)) + public StubService() : base(nameof(ClearSwapPositionsScenarioTest)) { } diff --git a/UnitTestProject/Modules/SwapModule/ComposePageScenarioTest.cs b/UnitTestProject/Modules/SwapModule/ComposePageScenarioTest.cs index ccbb7672..020ec6c0 100644 --- a/UnitTestProject/Modules/SwapModule/ComposePageScenarioTest.cs +++ b/UnitTestProject/Modules/SwapModule/ComposePageScenarioTest.cs @@ -22,13 +22,13 @@ namespace YLErp.Modules.SwapModule #region Stub - private sealed class StubEodService : SwapEodPositionService + private sealed class StubEodService : TestableSwapEodPositionService { - public List CreatedEodPositions { get; } = new(); - public int ClientCashCallCount { get; private set; } - private int _nextId = 1; + // 输出别名(转发到基类捕获属性,保持测试断言不变) + public List CreatedEodPositions => PersistedPositions; + public int ClientCashCallCount => ClientCashCalls.Count; - public StubEodService() : base(new OptUserInfo(0, nameof(ComposePageScenarioTest), OptUserFrom.UnitTest)) + public StubEodService() : base(nameof(ComposePageScenarioTest)) { } @@ -56,13 +56,7 @@ namespace YLErp.Modules.SwapModule protected override swap_event AddSwapEvent(DateTime tradeDate, int swapTradeId, int eventType, string data, int clientCashId, bool save, string reason) { - return new swap_event { id = _nextId++, SwapTradeId = swapTradeId, EventType = eventType, ValueDate = tradeDate, EventData = data }; - } - - protected override int AddClientCash(trade td, double amount, string action, DateTime valueDate) - { - ClientCashCallCount++; - return _nextId++; + return new swap_event { id = 1, SwapTradeId = swapTradeId, EventType = eventType, ValueDate = tradeDate, EventData = data }; } protected override void SaveEodSwapRecord(trade td, DateTime settleDate, DateTime preSettleDate) @@ -75,17 +69,6 @@ namespace YLErp.Modules.SwapModule // 不做任何事(测试无历史事件需清理) } - protected override void PersistEodSwapPosition(eod_swap_position position) - { - if (position.id == 0) position.id = _nextId++; - CreatedEodPositions.Add(position); - } - - protected override void SaveAllChanges() { } - - protected override double GetCurrencyRate(string quoteCurrency, string settlementCurrency, DateTime valueDate, bool seekPreday, CurrencyRateType currencyRateType) - => 1.0; - // override SaveEodPosition:捕获生成的 eod,绕过 UpdateSwapPosition 连库 protected override decimal SaveEodPosition(eod_swap_position newEodPayPosition, trade td, swap_flow_event eventFlow, diff --git a/UnitTestProject/Modules/SwapModule/DealFloatPositionsScenarioTest.cs b/UnitTestProject/Modules/SwapModule/DealFloatPositionsScenarioTest.cs index 4c509c8f..a0b076f1 100644 --- a/UnitTestProject/Modules/SwapModule/DealFloatPositionsScenarioTest.cs +++ b/UnitTestProject/Modules/SwapModule/DealFloatPositionsScenarioTest.cs @@ -25,7 +25,7 @@ namespace YLErp.Modules.SwapModule #region Stub - private sealed class StubEodService : SwapEodPositionService + private sealed class StubEodService : TestableSwapEodPositionService { // 可注入的外部数据 public decimal UnderlyingPrice { get; set; } = 1.00m; @@ -34,7 +34,7 @@ namespace YLErp.Modules.SwapModule public decimal TaxRate { get; set; } = 0m; public string UnderlyingCode { get; set; } = "210210.IB"; - public StubEodService() : base(new OptUserInfo(0, nameof(DealFloatPositionsScenarioTest), OptUserFrom.UnitTest)) + public StubEodService() : base(nameof(DealFloatPositionsScenarioTest)) { } @@ -55,11 +55,6 @@ namespace YLErp.Modules.SwapModule return BondPayment; } - protected override void SaveAllChanges() { } - - protected override double GetCurrencyRate(string quoteCurrency, string settlementCurrency, DateTime valueDate, bool seekPreday, CurrencyRateType currencyRateType) - => 1.0; - // DealFloatPositions 和子方法都是 protected,通过 public 包装暴露 public List ExecuteDealFloatPositions( List posiList, List realPosiList, diff --git a/UnitTestProject/Modules/SwapModule/DealInterestsGoldenReplayTest.cs b/UnitTestProject/Modules/SwapModule/DealInterestsGoldenReplayTest.cs index 0542fb5b..dafec3db 100644 --- a/UnitTestProject/Modules/SwapModule/DealInterestsGoldenReplayTest.cs +++ b/UnitTestProject/Modules/SwapModule/DealInterestsGoldenReplayTest.cs @@ -26,29 +26,29 @@ namespace YLErp.Modules.SwapModule #region Stub(复用 DealInterestsScenarioTest 的模式) - private sealed class StubEodService : SwapEodPositionService + private sealed class StubEodService : TestableSwapEodPositionService { - public List PersistedPositions { get; } = new(); - private int _nextId = 1; - - public StubEodService() : base(new OptUserInfo(0, nameof(DealInterestsGoldenReplayTest), OptUserFrom.UnitTest)) + public StubEodService() : base(nameof(DealInterestsGoldenReplayTest)) { } - protected override void PersistEodSwapPosition(eod_swap_position position) - { - if (position.id == 0) position.id = _nextId++; - PersistedPositions.Add(position); - } - protected override void SaveAllChanges() { } - protected override double GetCurrencyRate(string q, string s, DateTime d, bool p, CurrencyRateType t) => 1.0; - public void ExecuteSaveEodInterestPosition( eod_swap_position eodPayPosition, swap_position position, trade td, DateTime valueDate, List flowEvents) { SaveEodInterestPosition(eodPayPosition, null, position, td, valueDate, flowEvents); } + + // public 包装:直接调用 protected virtual DealInterests(录制场景2用) + public void ExecuteDealInterestsForRecord( + List interestList, List eodPositions, + DateTime settleDate, trade td, + decimal posiLongNational, decimal grossPrice, decimal orginPv) + { + DealInterests(interestList, eodPositions, new List(), + settleDate, td, new List(), new List(), null, + posiLongNational, 0m, 0m, grossPrice, orginPv); + } } #endregion @@ -189,17 +189,11 @@ namespace YLErp.Modules.SwapModule }; var service = new StubEodService(); - // 通过反射调 DealInterests(copy 分支需要 CalcSwapInterests) - var method = typeof(SwapEodPositionService).GetMethod("DealInterests", - System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); - method.Invoke(service, new object[] - { + // 直接调用 protected virtual DealInterests(copy 分支需要 CalcSwapInterests) + service.ExecuteDealInterestsForRecord( new List { position }, new List { preEod }, - new List(), - settleDate, td, new List(), new List(), null, - Principal, 0m, 0m, 1m, Principal - }); + settleDate, td, Principal, 1m, Principal); if (service.PersistedPositions.Count == 0) { diff --git a/UnitTestProject/Modules/SwapModule/DealInterestsScenarioTest.cs b/UnitTestProject/Modules/SwapModule/DealInterestsScenarioTest.cs index 1631b512..52a1eaac 100644 --- a/UnitTestProject/Modules/SwapModule/DealInterestsScenarioTest.cs +++ b/UnitTestProject/Modules/SwapModule/DealInterestsScenarioTest.cs @@ -40,31 +40,12 @@ namespace YLErp.Modules.SwapModule /// - PersistEodSwapPosition:收集到列表而非写库 /// - GetCurrencyRate:返回 1.0(本币) /// - private sealed class StubEodPositionService : SwapEodPositionService + private sealed class StubEodPositionService : TestableSwapEodPositionService { - public List PersistedPositions { get; } = new(); - - public StubEodPositionService() : base(new OptUserInfo(0, nameof(DealInterestsScenarioTest), OptUserFrom.UnitTest)) + public StubEodPositionService() : base(nameof(DealInterestsScenarioTest)) { } - protected override void PersistEodSwapPosition(eod_swap_position position) - { - // 收集到列表,不写库。如果 id=0 模拟新增。 - if (position.id == 0) position.id = PersistedPositions.Count + 1; - PersistedPositions.Add(position); - } - - protected override void SaveAllChanges() - { - // 不做任何事(内存模式) - } - - protected override double GetCurrencyRate(string quoteCurrency, string settlementCurrency, DateTime valueDate, bool seekPreday, CurrencyRateType currencyRateType) - { - return 1.0; // 本币,汇率=1 - } - // override CalcSwapInterests:用真实 SwapDealService 算(固定利率不需 mock 浮动利率) // 生产代码默认实现也是 new SwapDealService(this).GetInterests(...),这里保持一致 // 但 SwapDealService 内部 TryGetFloatRate 会连库——固定利率(FloatRateUnderlyingCode=null)不会触发 @@ -94,21 +75,16 @@ namespace YLErp.Modules.SwapModule return PersistedPositions.LastOrDefault(); } - // public 包装:调用 DealInterests(通过反射,因为参数太多不好包) + // public 包装:直接调用 protected virtual DealInterests(已改为 virtual,无需反射) public void ExecuteDealInterests( List interestList, List eodPositions, DateTime settleDate, trade td, List flowEvents, decimal posiLongNational, decimal posiShortNational, decimal closeNational, decimal grossPrice, decimal orginPv) { - var method = typeof(SwapEodPositionService).GetMethod("DealInterests", - System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); - method.Invoke(this, new object[] - { - interestList, eodPositions, new List(), + DealInterests(interestList, eodPositions, new List(), settleDate, td, flowEvents, new List(), null, - posiLongNational, posiShortNational, closeNational, grossPrice, orginPv - }); + posiLongNational, posiShortNational, closeNational, grossPrice, orginPv); } } diff --git a/UnitTestProject/Modules/SwapModule/GLMS20260105GoldenTest.cs b/UnitTestProject/Modules/SwapModule/GLMS20260105GoldenTest.cs index b70cdb0a..80280364 100644 --- a/UnitTestProject/Modules/SwapModule/GLMS20260105GoldenTest.cs +++ b/UnitTestProject/Modules/SwapModule/GLMS20260105GoldenTest.cs @@ -212,10 +212,10 @@ namespace YLErp.Modules.SwapModule #region 回放用 Stub - private sealed class ReplayStubService : SwapEodPositionService + private sealed class ReplayStubService : TestableSwapEodPositionService { private readonly string _underlyingCode; - public ReplayStubService(string underlyingCode) : base(new OptUserInfo(0, "Replay", OptUserFrom.UnitTest)) + public ReplayStubService(string underlyingCode) : base("Replay") { _underlyingCode = underlyingCode; } @@ -238,9 +238,6 @@ namespace YLErp.Modules.SwapModule return 0m; } - protected override void SaveAllChanges() { } - protected override double GetCurrencyRate(string q, string s, DateTime d, bool p, CurrencyRateType t) => 1.0; - public eod_swap_position ExecuteUpdateEodPosition( swap_position swapPosition, eod_swap_position eod, trade td, DateTime valueDate, DateTime preSettleDate, List unwindEvents) diff --git a/UnitTestProject/Modules/SwapModule/MultiUnwindDividendConservationTest.cs b/UnitTestProject/Modules/SwapModule/MultiUnwindDividendConservationTest.cs index 9dcb190a..c152645f 100644 --- a/UnitTestProject/Modules/SwapModule/MultiUnwindDividendConservationTest.cs +++ b/UnitTestProject/Modules/SwapModule/MultiUnwindDividendConservationTest.cs @@ -40,11 +40,11 @@ namespace YLErp.Modules.SwapModule /// CalcBondPayment 改为按天数 × 持仓线性函数,使 TdPosiDividend 真实随 /// "天数 × 剩余持仓"变化——这是验证多日递推守恒的前提。 /// - private sealed class StubEodService : SwapEodPositionService + private sealed class StubEodService : TestableSwapEodPositionService { private readonly decimal _dailyRatePerUnit; - public StubEodService(decimal dailyRatePerUnit) : base(new OptUserInfo(0, nameof(MultiUnwindDividendConservationTest), OptUserFrom.UnitTest)) + public StubEodService(decimal dailyRatePerUnit) : base(nameof(MultiUnwindDividendConservationTest)) { _dailyRatePerUnit = dailyRatePerUnit; } @@ -66,14 +66,6 @@ namespace YLErp.Modules.SwapModule return 1.00m; } - protected override void SaveAllChanges() { } - - // 注意:UpdateEodPosition.cs:1645 直接 new EodCurrencyRateService,不走此 seam; - // 但 trade.QuoteCurrency == trade.SettlementCurrency == "CNY" 时, - // EodCurrencyRateService.GetEodCurrencyRate 会在查库前短路返回 Rate=1(cs:268-281) - protected override double GetCurrencyRate(string quoteCurrency, string settlementCurrency, DateTime valueDate, bool seekPreday, CurrencyRateType currencyRateType) - => 1.0; - // 暴露 protected UpdateEodPosition(参考 GLMS20260105GoldenTest.ReplayStubService:244) public eod_swap_position ExecuteUpdateEodPosition( swap_position swapPosition, eod_swap_position eod, trade td, diff --git a/UnitTestProject/Modules/SwapModule/SwapPositionComposeScenarioTest.cs b/UnitTestProject/Modules/SwapModule/SwapPositionComposeScenarioTest.cs index 86c3dac1..8b69eea1 100644 --- a/UnitTestProject/Modules/SwapModule/SwapPositionComposeScenarioTest.cs +++ b/UnitTestProject/Modules/SwapModule/SwapPositionComposeScenarioTest.cs @@ -23,7 +23,7 @@ namespace YLErp.Modules.SwapModule /// 继承 SwapEodPositionService,override SwapPositionCompose 路径上的 seam。 /// 适配当前分支 seam 签名(GetUnderlyingPrice 带 out、GetCurrencyRate 返回 double 等)。 /// - private sealed class TestableSwapEodService : SwapEodPositionService + private sealed class TestableSwapEodService : TestableSwapEodPositionService { private readonly List _trades; private readonly List _positions; @@ -34,15 +34,15 @@ namespace YLErp.Modules.SwapModule private readonly decimal _price; private readonly decimal _vobp; - public List CreatedEodPositions { get; } = new(); - public List<(double amount, string action)> ClientCashCalls { get; } = new(); + // 输出别名(转发到基类捕获属性) + public List CreatedEodPositions => PersistedPositions; public TestableSwapEodService( List trades, List positions, List eodPositions, List eodSwaps, List extends, List flowEvents, decimal price = 100m, decimal vobp = 0m) - : base(new OptUserInfo(0, nameof(SwapPositionComposeScenarioTest), OptUserFrom.UnitTest)) + : base(nameof(SwapPositionComposeScenarioTest)) { _trades = trades; _positions = positions; _eodPositions = eodPositions; _eodSwaps = eodSwaps; _extends = extends; _flowEvents = flowEvents; @@ -67,13 +67,9 @@ namespace YLErp.Modules.SwapModule { vobp = _vobp; return _price; } protected override decimal CalcBondPayment(string underlyingCode, DateTime fromDate, DateTime toDate, decimal qty, int shortRatio, int directionRatio) => 0m; - // 持久化/事务 seam override - protected override void PersistEodSwapPosition(eod_swap_position position) { CreatedEodPositions.Add(position); } + // 持久化/事务 seam override(PersistEodSwapPosition/SaveAllChanges/GetCurrencyRate/AddClientCash 由基类提供) protected override void SaveEodSwapRecord(trade td, DateTime settleDate, DateTime preSettleDate) { } - protected override void SaveAllChanges() { } protected override void ExecuteInTransaction(Action action) => action(); - protected override int AddClientCash(trade td, double amount, string action, DateTime valueDate) - { ClientCashCalls.Add((amount, action)); return ClientCashCalls.Count; } protected override void ClearSwapPositionsForCompose(trade td, DateTime tradeDate, List eventTypes) { } public override void ClearSwapPositions(trade td, DateTime valueDate, List eventTypes, bool delAfter) { } protected override swap_event AddSwapEvent(DateTime tradeDate, int swapTradeId, int eventType, string data, int clientCashId, bool save, string reason) @@ -85,7 +81,6 @@ namespace YLErp.Modules.SwapModule decimal closePosiNotionalValue, decimal closePrecent, int eventType, bool tdClose, bool needPrice, decimal grossPrice, decimal orginPv, bool add = false, bool settment = true, bool newCalcLast = false, List closeList = null) => new List(); - protected override double GetCurrencyRate(string quoteCurrency, string settlementCurrency, DateTime valueDate, bool seekPreday, CurrencyRateType currencyRateType) => 1.0; public void ExecuteSwapPositionCompose(DateTime settleDate, DateTime preSettleDate) => SwapPositionCompose(settleDate, preSettleDate, null); diff --git a/UnitTestProject/Modules/SwapModule/TestableSwapEodPositionService.cs b/UnitTestProject/Modules/SwapModule/TestableSwapEodPositionService.cs new file mode 100644 index 00000000..181e8cd2 --- /dev/null +++ b/UnitTestProject/Modules/SwapModule/TestableSwapEodPositionService.cs @@ -0,0 +1,68 @@ +using YLErp.DBModels; +using YLErp.DBModels.Enums; +using YLErp.Model; + +namespace YLErp.Modules.SwapModule +{ + /// + /// SwapEodPositionService 的可测试化基类(纯内存,不连库)。 + /// ============================================================================ + /// 收敛各 ScenarioTest 中 Stub 子类的重复 override: + /// - PersistEodSwapPosition:收集到列表,不写库 + /// - SaveAllChanges:no-op + /// - GetCurrencyRate:返回 1.0(本币) + /// - 统一构造函数(注入 OptUserInfo,标记 UnitTest 来源) + /// + /// 暴露 PersistedPositions / SaveChangesCount / ClientCashCalls 等输出捕获属性, + /// 供断言使用。各测试子类按需再 override 业务 seam(FindTrade/GetUnderlyingPrice 等)。 + /// + /// 设计原则: + /// - 只收敛 8/8 Stub 都重复的高频 override,不预设业务数据注入方式 + /// (ComposePage 用属性字典、SwapPositionCompose 用构造函数 List,差异留给子类) + /// - 不提供 Execute* 包装器(签名各异且大多只出现 1-2 次,留在各子类避免基类膨胀) + /// ============================================================================ + /// + public class TestableSwapEodPositionService : SwapEodPositionService + { + /// 捕获所有持久化的 eod 持仓(按调用顺序) + public List PersistedPositions { get; } = new(); + + /// SaveAllChanges 调用次数 + public int SaveChangesCount { get; private set; } + + /// AddClientCash 调用记录(金额, 操作) + public List<(double amount, string action)> ClientCashCalls { get; } = new(); + + /// 自增 id 模拟器(新增 eod 时分配 id) + private int _nextId = 1; + + protected TestableSwapEodPositionService(string testName) + : base(new OptUserInfo(0, testName ?? nameof(TestableSwapEodPositionService), OptUserFrom.UnitTest)) + { + } + + // ===== 高频 seam override(8/8 Stub 都重复,收敛到基类)===== + + protected override void PersistEodSwapPosition(eod_swap_position position) + { + if (position.id == 0) position.id = _nextId++; + PersistedPositions.Add(position); + } + + protected override void SaveAllChanges() + { + SaveChangesCount++; + } + + protected override double GetCurrencyRate(string quoteCurrency, string settlementCurrency, DateTime valueDate, bool seekPreday, CurrencyRateType currencyRateType) + { + return 1.0; // 本币,汇率=1 + } + + protected override int AddClientCash(trade td, double amount, string action, DateTime valueDate) + { + ClientCashCalls.Add((amount, action)); + return _nextId++; + } + } +} diff --git a/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs b/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs index 55f0f88c..509a39cd 100644 --- a/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs +++ b/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs @@ -395,7 +395,7 @@ namespace YLErp.Modules.SwapModule /// 自动互换集合 /// 多空组合平仓利息腿信息 /// 上一日终框架合约 - protected void DealInterests(List interestList, + protected virtual void DealInterests(List interestList, List eodPositions, List todyEodPositions, DateTime settleDate,