test(swap): 阶段3 扩展DealInterests测试-普通日归档值+多日连续守恒

依赖阶段1c的CalcSwapInterests虚方法接缝,现在普通日分支(copy)
也可纯内存测试。

新增/改进的测试:
- DI_BRANCH_001: 改为真正的普通日归档值验证(InterestIncomeSum
  前日+1天新计=2天利息),不再是Inconclusive
- DI_MULTI_001: 连续5天收盘归档,验证InterestIncomeSum线性递增
  (每天+1天利息,5天后=5×DailyInterest)

StubEodPositionService扩展:
- override CalcSwapInterests(用真实SwapDealService算固定利率利息)
- ExecuteDealInterests public包装(替代反射调用)
- 删除旧的反射辅助方法

验证: 89(T0/T1)+5(DealInterests)=94全通过,无回归。
This commit is contained in:
hjhan
2026-07-01 18:21:51 +08:00
parent 638803c497
commit 775d7a5881
@@ -65,6 +65,26 @@ namespace YLErp.Modules.SwapModule
return 1.0; // 本币,汇率=1
}
// override CalcSwapInterests:用真实 SwapDealService 算(固定利率不需 mock 浮动利率)
// 生产代码默认实现也是 new SwapDealService(this).GetInterests(...),这里保持一致
// 但 SwapDealService 内部 TryGetFloatRate 会连库——固定利率(FloatRateUnderlyingCode=null)不会触发
protected override List<swap_flow_event> CalcSwapInterests(
trade td, trade_extend tradeExtend,
DateTime valueDate, DateTime unwindDate,
List<eod_swap_position> eodPositions, List<swap_position> positions,
decimal posiNotionalValue, decimal posiLongNotionalValue, decimal posiShortNotionalValue,
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)
{
return new SwapDealService(this).GetInterests(td, tradeExtend, valueDate, unwindDate,
eodPositions, positions, posiNotionalValue, posiLongNotionalValue, posiShortNotionalValue,
closePosiNotionalValue, closePrecent, eventType, tdClose, needPrice,
grossPrice, orginPv, add, settment, newCalcLast, closeList);
}
// public 包装:让测试能调用 protected 方法
public eod_swap_position ExecuteSaveEodInterestPosition(
eod_swap_position eodPayPosition, eod_swap_position newEodPayPosition,
@@ -73,6 +93,23 @@ namespace YLErp.Modules.SwapModule
SaveEodInterestPosition(eodPayPosition, newEodPayPosition, position, td, valueDate, flowEvents);
return PersistedPositions.LastOrDefault();
}
// public 包装:调用 DealInterests(通过反射,因为参数太多不好包)
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>(),
settleDate, td, flowEvents, new List<swap_flow_event>(), null,
posiLongNational, posiShortNational, closeNational, grossPrice, orginPv
});
}
}
#endregion
@@ -265,40 +302,37 @@ namespace YLErp.Modules.SwapModule
/// flowEvents 为空,insterval=nullhasSwap=falsehasClose=false
/// → 应走 SaveEodInterestPositionCopycs:338
/// ---------------------------------------------------------------
/// <summary>
/// [DI_BRANCH_001] 普通日收盘归档:InterestIncomeSum 每天递增1天利息
/// ---------------------------------------------------------------
/// 前日待实现=1天利息,今日收盘(无互换无平仓),应变成2天利息。
/// 验证 copy 分支(SaveEodInterestPositionCopy)的 InterestIncomeSum 公式。
/// ---------------------------------------------------------------
/// </summary>
[TestMethod]
public void DI_BRANCH_001_普通日走copy分支()
public void DI_BRANCH_001_普通日归档待实现递增()
{
var service = new StubEodPositionService();
var td = CreateTrade();
var position = CreateInterestPosition();
var settleDate = new DateTime(2026, 5, 11);
var preEod = CreatePreEod(settleDate.AddDays(-1), DailyInterest);
var settleDate = new DateTime(2026, 4, 28); // 第2天
var preEod = CreatePreEod(settleDate.AddDays(-1), DailyInterest); // 前日=1天利息
// 普通日:flowEvents 为空
var interestList = new List<swap_position> { position };
var eodPositions = new List<eod_swap_position> { preEod };
// 普通日:无互换无平仓,InterestSwapInterval=null(当天非观察日)
position.InterestSwapInterval = null;
// DealInterests 需要 SwapIntervalList(当天不是观察日 → insterval=null
position.InterestSwapInterval = null; // 清空,确保当天无观察日
service.ExecuteDealInterests(
new List<swap_position> { position },
new List<eod_swap_position> { preEod },
settleDate, td, new List<swap_flow_event>(),
Principal, 0m, 0m, 1m, Principal);
// 调 DealInterestsprotected,通过 Stub 类的 protected 访问)
// 注意:DealInterests 调 SaveEodInterestPositionCopy,后者调 GetInterests
// GetInterests 需要 SwapDealService 的接缝。当前 StubEodPositionService 没有 override 它。
// 这个测试先验证分支不抛异常(分支选择正确),具体值验证待 CalcSwapInterests 接缝
// TODO: 阶段1c 加 CalcSwapInterests 接缝后补充值断言
try
{
CallDealInterests(service, interestList, eodPositions, settleDate, td,
new List<swap_flow_event>(), new List<swap_flow_event>(),
Principal, 0m, 0m, 1m, Principal);
// 如果到了这里说明没抛异常(可能 GetInterests 成功了,或者没走到)
Assert.IsTrue(true, "普通日分支执行完成");
}
catch (Exception ex) when (ex.Message.Contains("GetInterests") || ex.Message.Contains("浮动利率"))
{
Assert.Inconclusive("需要 CalcSwapInterests 接缝才能测试普通日分支的值。异常: " + ex.Message);
}
Assert.IsTrue(service.PersistedPositions.Count > 0, "应生成eod");
var result = service.PersistedPositions[0];
// 普通日:InterestIncomeSum 应 = 前日 + 当天新计 = 1天 + 1天 = 2天
AssertDecimal(DailyInterest * 2, result.InterestIncomeSum,
$"普通日后 InterestIncomeSum 应=2天利息({DailyInterest * 2:F6})");
Console.WriteLine($"普通日归档:InterestIncomeSum={result.InterestIncomeSum:F6} = 2×{DailyInterest:F6} ✅");
}
/// <summary>
@@ -325,8 +359,8 @@ namespace YLErp.Modules.SwapModule
var interestList = new List<swap_position> { position };
var eodPositions = new List<eod_swap_position> { preEod };
CallDealInterests(service, interestList, eodPositions, settleDate, td,
new List<swap_flow_event> { swapEvent }, new List<swap_flow_event>(),
service.ExecuteDealInterests(interestList, eodPositions, settleDate, td,
new List<swap_flow_event> { swapEvent },
Principal, 0m, 0m, 1m, Principal);
// 互换分支应生成1条 eod
@@ -339,32 +373,53 @@ namespace YLErp.Modules.SwapModule
#endregion
#region protected DealInterests
// ================================================================
// 场景3:多日守恒——连续收盘归档,InterestIncomeSum 应线性递增
// ================================================================
#region 3
/// <summary>
/// DealInterests 是 protected,通过反射调用(MSTest 不支持 InternalsVisibleTo 方式)。
/// 也可以在 StubEodPositionService 里加 public 包装方法,但反射更简洁且不改生产类。
/// [DI_MULTI_001] 连续5天普通日收盘归档,InterestIncomeSum 每天递增1天利息
/// ---------------------------------------------------------------
/// 从4/27(首日)开始,连续收盘到5/1,验证 InterestIncomeSum 线性递增。
/// 每天收盘后 InterestIncomeSum 应 = 天数 × DailyInterest。
/// ---------------------------------------------------------------
/// </summary>
private static void CallDealInterests(
SwapEodPositionService service,
List<swap_position> interestList,
List<eod_swap_position> eodPositions,
DateTime settleDate,
trade td,
List<swap_flow_event> flowEvents,
List<swap_flow_event> autoInterests,
decimal posiLongNational, decimal posiShortNational,
decimal closeNational, decimal grossPrice, decimal orginPv = Principal)
[TestMethod]
public void DI_MULTI_001_连续5天归档待实现线性递增()
{
var method = typeof(SwapEodPositionService).GetMethod("DealInterests",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
Assert.IsNotNull(method, "DealInterests 方法应存在");
method.Invoke(service, new object[]
var td = CreateTrade();
var position = CreateInterestPosition();
position.InterestSwapInterval = null; // 无观察日
decimal runningIncomeSum = 0m;
var runningDate = StartDate;
for (int day = 0; day < 5; day++)
{
interestList, eodPositions, new List<eod_swap_position>(),
settleDate, td, flowEvents, autoInterests, null,
posiLongNational, posiShortNational, closeNational, grossPrice, orginPv
});
var service = new StubEodPositionService();
var preEod = CreatePreEod(runningDate.AddDays(-1), runningIncomeSum);
service.ExecuteDealInterests(
new List<swap_position> { position },
new List<eod_swap_position> { preEod },
runningDate, td, new List<swap_flow_event>(),
Principal, 0m, 0m, 1m, Principal);
Assert.IsTrue(service.PersistedPositions.Count > 0, $"第{day + 1}天应生成eod");
var result = service.PersistedPositions[0];
// 首日 InterestIncomeSum = 1天利息,后续每天+1天利息
decimal expected = DailyInterest * (day + 1);
AssertDecimal(expected, result.InterestIncomeSum,
$"第{day + 1}天 InterestIncomeSum 应={(day + 1)}天利息");
runningIncomeSum = result.InterestIncomeSum;
runningDate = runningDate.AddDays(1);
}
Console.WriteLine($"连续5天归档:InterestIncomeSum 从0递增到{runningIncomeSum:F6} = 5×{DailyInterest:F6} ✅");
}
#endregion