refactor(swap-test): 抽取 TestableSwapEodPositionService 公共基类收敛重复 Stub
- 新建 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失败
This commit is contained in:
@@ -21,7 +21,7 @@ namespace YLErp.Modules.SwapModule
|
||||
|
||||
#region Stub
|
||||
|
||||
private sealed class StubService : SwapEodPositionService
|
||||
private sealed class StubService : TestableSwapEodPositionService
|
||||
{
|
||||
// 注入的内存数据
|
||||
public List<swap_flow_event> FlowEvents { get; set; } = new();
|
||||
@@ -30,7 +30,7 @@ namespace YLErp.Modules.SwapModule
|
||||
|
||||
public List<ClientCashInCashOut> DeletedRecords { get; } = new();
|
||||
|
||||
public StubService() : base(new OptUserInfo(0, nameof(ClearSwapPositionsScenarioTest), OptUserFrom.UnitTest))
|
||||
public StubService() : base(nameof(ClearSwapPositionsScenarioTest))
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -22,13 +22,13 @@ namespace YLErp.Modules.SwapModule
|
||||
|
||||
#region Stub
|
||||
|
||||
private sealed class StubEodService : SwapEodPositionService
|
||||
private sealed class StubEodService : TestableSwapEodPositionService
|
||||
{
|
||||
public List<eod_swap_position> CreatedEodPositions { get; } = new();
|
||||
public int ClientCashCallCount { get; private set; }
|
||||
private int _nextId = 1;
|
||||
// 输出别名(转发到基类捕获属性,保持测试断言不变)
|
||||
public List<eod_swap_position> 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,
|
||||
|
||||
@@ -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<eod_swap_position> ExecuteDealFloatPositions(
|
||||
List<swap_position> posiList, List<swap_position> realPosiList,
|
||||
|
||||
@@ -26,29 +26,29 @@ namespace YLErp.Modules.SwapModule
|
||||
|
||||
#region Stub(复用 DealInterestsScenarioTest 的模式)
|
||||
|
||||
private sealed class StubEodService : SwapEodPositionService
|
||||
private sealed class StubEodService : TestableSwapEodPositionService
|
||||
{
|
||||
public List<eod_swap_position> 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<swap_flow_event> flowEvents)
|
||||
{
|
||||
SaveEodInterestPosition(eodPayPosition, null, position, td, valueDate, flowEvents);
|
||||
}
|
||||
|
||||
// public 包装:直接调用 protected virtual DealInterests(录制场景2用)
|
||||
public void ExecuteDealInterestsForRecord(
|
||||
List<swap_position> interestList, List<eod_swap_position> eodPositions,
|
||||
DateTime settleDate, trade td,
|
||||
decimal posiLongNational, decimal grossPrice, decimal orginPv)
|
||||
{
|
||||
DealInterests(interestList, eodPositions, new List<eod_swap_position>(),
|
||||
settleDate, td, new List<swap_flow_event>(), new List<swap_flow_event>(), 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<swap_position> { position },
|
||||
new List<eod_swap_position> { preEod },
|
||||
new List<eod_swap_position>(),
|
||||
settleDate, td, new List<swap_flow_event>(), new List<swap_flow_event>(), null,
|
||||
Principal, 0m, 0m, 1m, Principal
|
||||
});
|
||||
settleDate, td, Principal, 1m, Principal);
|
||||
|
||||
if (service.PersistedPositions.Count == 0)
|
||||
{
|
||||
|
||||
@@ -40,31 +40,12 @@ namespace YLErp.Modules.SwapModule
|
||||
/// - PersistEodSwapPosition:收集到列表而非写库
|
||||
/// - GetCurrencyRate:返回 1.0(本币)
|
||||
/// </summary>
|
||||
private sealed class StubEodPositionService : SwapEodPositionService
|
||||
private sealed class StubEodPositionService : TestableSwapEodPositionService
|
||||
{
|
||||
public List<eod_swap_position> 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<swap_position> interestList, List<eod_swap_position> eodPositions,
|
||||
DateTime settleDate, trade td, List<swap_flow_event> 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<eod_swap_position>(),
|
||||
DealInterests(interestList, eodPositions, new List<eod_swap_position>(),
|
||||
settleDate, td, flowEvents, new List<swap_flow_event>(), null,
|
||||
posiLongNational, posiShortNational, closeNational, grossPrice, orginPv
|
||||
});
|
||||
posiLongNational, posiShortNational, closeNational, grossPrice, orginPv);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<swap_flow_event> unwindEvents)
|
||||
|
||||
@@ -40,11 +40,11 @@ namespace YLErp.Modules.SwapModule
|
||||
/// CalcBondPayment 改为按天数 × 持仓线性函数,使 TdPosiDividend 真实随
|
||||
/// "天数 × 剩余持仓"变化——这是验证多日递推守恒的前提。
|
||||
/// </summary>
|
||||
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,
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace YLErp.Modules.SwapModule
|
||||
/// 继承 SwapEodPositionService,override SwapPositionCompose 路径上的 seam。
|
||||
/// 适配当前分支 seam 签名(GetUnderlyingPrice 带 out、GetCurrencyRate 返回 double 等)。
|
||||
/// </summary>
|
||||
private sealed class TestableSwapEodService : SwapEodPositionService
|
||||
private sealed class TestableSwapEodService : TestableSwapEodPositionService
|
||||
{
|
||||
private readonly List<trade> _trades;
|
||||
private readonly List<swap_position> _positions;
|
||||
@@ -34,15 +34,15 @@ namespace YLErp.Modules.SwapModule
|
||||
private readonly decimal _price;
|
||||
private readonly decimal _vobp;
|
||||
|
||||
public List<eod_swap_position> CreatedEodPositions { get; } = new();
|
||||
public List<(double amount, string action)> ClientCashCalls { get; } = new();
|
||||
// 输出别名(转发到基类捕获属性)
|
||||
public List<eod_swap_position> CreatedEodPositions => PersistedPositions;
|
||||
|
||||
public TestableSwapEodService(
|
||||
List<trade> trades, List<swap_position> positions,
|
||||
List<eod_swap_position> eodPositions, List<eod_swap> eodSwaps,
|
||||
List<trade_extend> extends, List<swap_flow_event> 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<int> eventTypes) { }
|
||||
public override void ClearSwapPositions(trade td, DateTime valueDate, List<int> 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<swap_flow_event> closeList = null) => new List<swap_flow_event>();
|
||||
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);
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
using YLErp.DBModels;
|
||||
using YLErp.DBModels.Enums;
|
||||
using YLErp.Model;
|
||||
|
||||
namespace YLErp.Modules.SwapModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 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 次,留在各子类避免基类膨胀)
|
||||
/// ============================================================================
|
||||
/// </summary>
|
||||
public class TestableSwapEodPositionService : SwapEodPositionService
|
||||
{
|
||||
/// <summary>捕获所有持久化的 eod 持仓(按调用顺序)</summary>
|
||||
public List<eod_swap_position> PersistedPositions { get; } = new();
|
||||
|
||||
/// <summary>SaveAllChanges 调用次数</summary>
|
||||
public int SaveChangesCount { get; private set; }
|
||||
|
||||
/// <summary>AddClientCash 调用记录(金额, 操作)</summary>
|
||||
public List<(double amount, string action)> ClientCashCalls { get; } = new();
|
||||
|
||||
/// <summary>自增 id 模拟器(新增 eod 时分配 id)</summary>
|
||||
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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user