fix(core-tests): 收敛PR298的nitpick问题

- 修复测试辅助类型的只读暴露、空安全和线程安全问题

- 更新异步查询结果命名与init属性XML文档,保持语义一致

- 同步ai-plan恢复点与验证真值,记录PR298 nitpick跟进
This commit is contained in:
gewuyou 2026-04-27 20:18:58 +08:00
parent f0a36de07c
commit fbf8f9f0a2
23 changed files with 95 additions and 48 deletions

View File

@ -11,7 +11,7 @@ public sealed class TestCommandWithResultV2 : ICommand<int>
private IArchitectureContext _context = null!;
/// <summary>
/// 获取或设置命令执行结果。
/// 获取命令执行结果;该值只能在对象初始化阶段设置
/// </summary>
public int Result { get; init; }

View File

@ -11,7 +11,7 @@ public sealed class TestQueryV2 : IQuery<int>
private IArchitectureContext _context = null!;
/// <summary>
/// 获取或设置查询返回值。
/// 获取查询返回值;该值只能在对象初始化阶段设置
/// </summary>
public int Result { get; init; }

View File

@ -1,3 +1,4 @@
using System.Threading;
using GFramework.Cqrs.Abstractions.Cqrs;
namespace GFramework.Core.Tests.Architectures;
@ -10,13 +11,20 @@ namespace GFramework.Core.Tests.Architectures;
public sealed class TrackingPipelineBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
/// <summary>
/// 获取当前测试进程中该请求类型对应的行为触发次数。
/// </summary>
public static int InvocationCount { get; set; }
private static int _invocationCount;
/// <summary>
/// 记录一次行为执行,然后继续执行下一个处理器。
/// 获取当前测试进程中该请求类型对应的行为触发次数。
/// 该计数器是按泛型闭包共享的静态状态,测试需要在每次运行前显式重置。
/// </summary>
public static int InvocationCount
{
get => Volatile.Read(ref _invocationCount);
set => Volatile.Write(ref _invocationCount, value);
}
/// <summary>
/// 以线程安全方式记录一次行为执行,然后继续执行下一个处理器。
/// </summary>
/// <param name="message">当前请求消息。</param>
/// <param name="next">下一个处理委托。</param>
@ -27,7 +35,7 @@ public sealed class TrackingPipelineBehavior<TRequest, TResponse> : IPipelineBeh
MessageHandlerDelegate<TRequest, TResponse> next,
CancellationToken cancellationToken)
{
InvocationCount++;
Interlocked.Increment(ref _invocationCount);
return await next(message, cancellationToken).ConfigureAwait(false);
}
}

View File

@ -43,7 +43,8 @@ internal class ComplexQuery : IQuery<ComplexResult>
/// <returns>此前通过 <see cref="SetContext" /> 绑定的上下文实例。</returns>
public IArchitectureContext GetContext()
{
return _context!;
return _context ?? throw new InvalidOperationException(
$"{nameof(SetContext)} must be called before {nameof(GetContext)}.");
}
/// <summary>

View File

@ -5,7 +5,7 @@ namespace GFramework.Core.Tests.Coroutine;
/// <summary>
/// 为协程测试提供固定时间步长的时间源。
/// </summary>
public class TestTimeSource : ITimeSource
public sealed class TestTimeSource : ITimeSource
{
/// <summary>
/// 获取当前累计时间。

View File

@ -49,7 +49,7 @@ public class EnvironmentTests
[Test]
public void Get_Should_Return_Value_When_Key_Exists()
{
_environment.Register("testKey", "testValue");
_environment.RegisterForTest("testKey", "testValue");
var result = _environment.Get<string>("testKey");
@ -73,7 +73,7 @@ public class EnvironmentTests
[Test]
public void Get_Should_ReturnNull_When_Type_Does_Not_Match()
{
_environment.Register("testKey", "testValue");
_environment.RegisterForTest("testKey", "testValue");
var result = _environment.Get<List<int>>("testKey");
@ -86,7 +86,7 @@ public class EnvironmentTests
[Test]
public void TryGet_Should_ReturnTrue_And_Value_When_Key_Exists()
{
_environment.Register("testKey", "testValue");
_environment.RegisterForTest("testKey", "testValue");
var result = _environment.TryGet<string>("testKey", out var value);
@ -112,7 +112,7 @@ public class EnvironmentTests
[Test]
public void TryGet_Should_ReturnFalse_When_Type_Does_Not_Match()
{
_environment.Register("testKey", "testValue");
_environment.RegisterForTest("testKey", "testValue");
var result = _environment.TryGet<List<int>>("testKey", out var value);
@ -126,7 +126,7 @@ public class EnvironmentTests
[Test]
public void GetRequired_Should_Return_Value_When_Key_Exists()
{
_environment.Register("testKey", "testValue");
_environment.RegisterForTest("testKey", "testValue");
var result = _environment.GetRequired<string>("testKey");
@ -149,7 +149,7 @@ public class EnvironmentTests
[Test]
public void Register_Should_Add_Value_To_Dictionary()
{
_environment.Register("newKey", "newValue");
_environment.RegisterForTest("newKey", "newValue");
var result = _environment.Get<string>("newKey");
@ -162,8 +162,8 @@ public class EnvironmentTests
[Test]
public void Register_Should_Overwrite_Existing_Value()
{
_environment.Register("testKey", "value1");
_environment.Register("testKey", "value2");
_environment.RegisterForTest("testKey", "value1");
_environment.RegisterForTest("testKey", "value2");
var result = _environment.Get<string>("testKey");

View File

@ -5,7 +5,7 @@ namespace GFramework.Core.Tests.Environment;
/// <summary>
/// 为环境相关测试提供可写注册入口的测试环境实现。
/// </summary>
public class TestEnvironment : EnvironmentBase
public sealed class TestEnvironment : EnvironmentBase
{
/// <summary>
/// 获取测试环境名称。
@ -13,11 +13,11 @@ public class TestEnvironment : EnvironmentBase
public override string Name { get; } = "TestEnvironment";
/// <summary>
/// 将测试数据注册到基础环境存储中,便于测试通过公开入口准备上下文。
/// 将测试数据注册到基础环境存储中,便于测试通过显式测试辅助入口准备上下文。
/// </summary>
/// <param name="key">要注册的环境键。</param>
/// <param name="value">要注册的环境值。</param>
public new void Register(string key, object value)
public void RegisterForTest(string key, object value)
{
base.Register(key, value);
}

View File

@ -212,7 +212,7 @@ public class AbstractAsyncQueryTests
Assert.That(result, Is.Not.Null);
Assert.That(result.Value, Is.EqualTo(20));
Assert.That(result.DoubleValue, Is.EqualTo(30));
Assert.That(result.TripleValue, Is.EqualTo(30));
}
/// <summary>

View File

@ -87,7 +87,7 @@ public class AsyncQueryExecutorTests
Assert.That(result, Is.Not.Null);
Assert.That(result.Value, Is.EqualTo(200));
Assert.That(result.DoubleValue, Is.EqualTo(300));
Assert.That(result.TripleValue, Is.EqualTo(300));
}
/// <summary>

View File

@ -25,7 +25,7 @@ public sealed class TestAsyncComplexQuery : AbstractAsyncQuery<TestAsyncQueryInp
var result = new TestAsyncQueryResult
{
Value = input.Value * 2,
DoubleValue = input.Value * 3
TripleValue = input.Value * 3
};
return Task.FromResult(result);

View File

@ -31,7 +31,7 @@ public sealed class TestAsyncComplexQueryV4 : AbstractAsyncQuery<TestAsyncQueryI
var result = new TestAsyncQueryResultV2
{
Value = input.Value * 2,
DoubleValue = input.Value * 3
TripleValue = input.Value * 3
};
return Task.FromResult(result);
}

View File

@ -8,7 +8,7 @@ namespace GFramework.Core.Tests.Query;
public sealed class TestAsyncQueryInput : IQueryInput
{
/// <summary>
/// 获取或设置查询值。
/// 获取查询值;该值只能在对象初始化阶段设置
/// </summary>
public int Value { get; init; }
}

View File

@ -6,12 +6,12 @@ namespace GFramework.Core.Tests.Query;
public sealed class TestAsyncQueryResult
{
/// <summary>
/// 获取或设置主结果值。
/// 获取主结果值;该值只能在对象初始化阶段设置
/// </summary>
public int Value { get; init; }
/// <summary>
/// 获取或设置派生的双重结果值
/// 获取派生的三倍结果值;该值只能在对象初始化阶段设置
/// </summary>
public int DoubleValue { get; init; }
public int TripleValue { get; init; }
}

View File

@ -6,12 +6,12 @@ namespace GFramework.Core.Tests.Query;
public sealed class TestAsyncQueryResultV2
{
/// <summary>
/// 获取或设置值
/// 获取值;该值只能在对象初始化阶段设置。
/// </summary>
public int Value { get; init; }
/// <summary>
/// 获取或设置双倍值
/// 获取三倍值;该值只能在对象初始化阶段设置。
/// </summary>
public int DoubleValue { get; init; }
public int TripleValue { get; init; }
}

View File

@ -245,7 +245,7 @@ public class StateMachineTests
[Test]
public async Task ChangeToAsync_WhenCurrentStateDeniesTransition_WithAsyncState_Should_Call_CanTransitionToAsync()
{
var state1 = new TestAsyncState { AllowTransition = false };
var state1 = new TestAsyncState { AllowTransitions = false };
var state2 = new TestStateV3();
_stateMachine.Register(state1);
_stateMachine.Register(state2);

View File

@ -10,7 +10,7 @@ public sealed class TestAsyncState : IState, IAsyncState
/// <summary>
/// 获取或设置是否允许向目标状态转移。
/// </summary>
public bool AllowTransition { get; set; } = true;
public bool AllowTransitions { get; set; } = true;
/// <summary>
/// 获取异步进入状态是否已被调用。
@ -80,7 +80,7 @@ public sealed class TestAsyncState : IState, IAsyncState
{
await Task.Delay(1).ConfigureAwait(false);
CanTransitionToCallCount++;
return AllowTransition;
return AllowTransitions;
}
/// <summary>

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using GFramework.Core.Abstractions.State;
using GFramework.Core.State;
@ -14,8 +15,8 @@ public class TestStateMachineSystemV5 : StateMachineSystem
/// 获取状态机当前维护的状态实例映射,供测试断言注册结果使用。
/// </summary>
/// <returns>状态类型到状态实例的只读视图。</returns>
public IDictionary<Type, IState> GetStates()
public IReadOnlyDictionary<Type, IState> GetStates()
{
return States;
return States as IReadOnlyDictionary<Type, IState> ?? new ReadOnlyDictionary<Type, IState>(States);
}
}

View File

@ -19,6 +19,7 @@ public class TestStateV5_2 : IState
/// <returns>始终返回 <see langword="true" /> 以简化状态机切换测试。</returns>
public bool CanTransitionTo(IState next)
{
_ = next;
return true;
}
@ -28,6 +29,7 @@ public class TestStateV5_2 : IState
/// <param name="previous">前一个状态。</param>
public void OnEnter(IState? previous)
{
_ = previous;
}
/// <summary>
@ -36,5 +38,6 @@ public class TestStateV5_2 : IState
/// <param name="next">下一个状态。</param>
public void OnExit(IState? next)
{
_ = next;
}
}

View File

@ -198,7 +198,7 @@ public class AbstractContextUtilityTests
Assert.That(utility.Destroyed, Is.True);
// 重置状态
utility.Destroyed = false;
utility.ResetDestroyedStateForTest();
// 第二次初始化和销毁
utility.Initialize();

View File

@ -14,12 +14,13 @@ public sealed class TestContextUtilityV1 : AbstractContextUtility
public bool Initialized { get; private set; }
/// <summary>
/// 获取或设置一个值,该值指示当前工具是否已执行销毁逻辑。
/// 获取一个值,该值指示当前工具是否已执行销毁逻辑。
/// </summary>
public bool Destroyed { get; set; }
public bool Destroyed { get; private set; }
/// <summary>
/// 获取一个值,该值指示测试初始化钩子是否已被调用。
/// 获取一个值,该值指示测试专用的 <see cref="OnInit" /> 钩子是否已被调用。
/// 该标记与 <see cref="Initialized" /> 共享同一执行时机,但单独暴露以便断言钩子本身已运行。
/// </summary>
public bool InitCalled { get; private set; }
@ -48,4 +49,12 @@ public sealed class TestContextUtilityV1 : AbstractContextUtility
{
Destroyed = true;
}
/// <summary>
/// 重置销毁标记,供同一实例的重复生命周期测试在下一轮初始化前清理观测状态。
/// </summary>
public void ResetDestroyedStateForTest()
{
Destroyed = false;
}
}

View File

@ -13,9 +13,9 @@ public sealed class TestContextUtilityV2 : AbstractContextUtility
public bool Initialized { get; private set; }
/// <summary>
/// 获取或设置一个值,该值指示当前工具是否已执行销毁逻辑。
/// 获取一个值,该值指示当前工具是否已执行销毁逻辑。
/// </summary>
public bool Destroyed { get; set; }
public bool Destroyed { get; private set; }
/// <summary>
/// 获取一个值,该值指示自定义初始化步骤是否已完成。

View File

@ -6,10 +6,11 @@
## 当前恢复点
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-085`
- 当前阶段:`Phase 85`
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-086`
- 当前阶段:`Phase 86`
- 当前焦点:
- `2026-04-27` 已按 `$gframework-batch-boot 100` 连续执行多波 `MA0048` 小切片,当前以 `GFramework.Core.Tests` 的测试辅助类型拆分为主
- `2026-04-27` 已按 `$gframework-pr-review` 收敛 `PR #298` 的有效 nitpick修复测试辅助类型的只读暴露、线程安全、空安全与文档一致性问题
- 本轮已完成 `ArchitectureContextTests``AsyncQueryExecutorTests``CommandExecutorTests``StateTests``StateMachineTests``StateMachineSystemTests``ArchitectureModulesBehaviorTests``ArchitectureAdditionalCqrsHandlersTests``QueryCoroutineExtensionsTests``ObjectPoolTests``AbstractContextUtilityTests` 等低风险单文件切片
- 当前仓库根权威基线已从 `353 Warning(s)` / `279` 个唯一位点下降到 `288 Warning(s)` / `214` 个唯一位点
- 当前分支下一波更适合转向 `GameContextTests.cs``ArchitectureServicesTests.cs``RegistryInitializationHookBaseTests.cs` 这类仍在 `GFramework.Core.Tests` 内、但已混入 `CS8766` / `MA0016` 的小型混合切片
@ -19,15 +20,15 @@
- 当前 `origin/main` 基线提交为 `7cfdd2c``2026-04-27T16:59:57+08:00`)。
- 当前直接验证结果:
- `dotnet build GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release`
- 最新结果:成功;`0 Warning(s)``0 Error(s)`
- 最新结果:成功;`28 Warning(s)``0 Error(s)`;当前 warning 来自 `GameContextTests.cs``ArchitectureServicesTests.cs``RegistryInitializationHookBaseTests.cs` 等既有热点
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-build`
- 最新结果:成功;`1610` 通过、`0` 失败
- `dotnet clean`
- 最新结果:成功;已刷新仓库根 non-incremental 基线
- `dotnet build`
- 最新结果:成功;`288 Warning(s)``0 Error(s)`,唯一位点 `214`
- `dotnet build GFramework.Game/GFramework.Game.csproj -c Release`
- 最新结果:成功;`0 Warning(s)``0 Error(s)`
- `dotnet build GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release`
- 最新结果:成功;`0 Warning(s)``0 Error(s)`
- `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~YamlConfigLoaderTests.ReadYamlAsync_Should_Preserve_OperationCanceledException_When_Cancellation_Is_Requested"`
- 最新结果:成功;`1` 通过、`0` 失败
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~MicrosoftDiContainerTests.GetAllByPriority_Should_Sort_By_Priority_Ascending"`

View File

@ -1,5 +1,29 @@
# Analyzer Warning Reduction 追踪
## 2026-04-27 — RP-086
### 阶段:收敛 PR #298 的 CodeRabbit nitpick follow-up
- 触发背景:
- 用户再次执行 `$gframework-pr-review` 后,要求按 `PR #298` 的 nitpick comments 收敛仍然适用的问题
- 复核 PR 真值后确认当前分支无 failed checks、无 open review threads但仍有一批测试辅助类型的可维护性 nitpick 值得本地落地
- 主线程实施:
- 校验 `TestStateMachineSystemV5``ComplexQuery``TrackingPipelineBehavior``TestEnvironment``TestContextUtilityV1/V2` 等改动后,分别修复可变内部状态暴露、`_context!` 空抑制、静态计数器非原子递增、`new Register(...)` 测试辅助入口和生命周期标记公开 setter 问题
- 统一更新 `TestQueryV2``TestCommandWithResultV2``TestAsyncQueryInput``TestAsyncQueryResult*` 的 XML 文档,使 `init` 属性语义与文档一致
- 将三倍结果属性从 `DoubleValue` 更名为 `TripleValue`,同步更新 `TestAsyncComplexQuery*` 与相关断言,避免名称与 `* 3` 的行为不一致
- 精简 active tracking移除重复的 `GFramework.Core.Tests` Release build 记录,并把该项目的当前真值修正为 `28 Warning(s)`
- 验证里程碑:
- `dotnet build GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release`
- 结果:成功;`28 Warning(s)``0 Error(s)`
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-build`
- 结果:成功;`1610` 通过、`0` 失败
- 当前结论:
- `PR #298` 中仍然适用的低风险 nitpick 已完成收敛,且没有为当前 touched files 引入新的构建 warning 或测试回归
- `GFramework.Core.Tests` 的剩余 warning 仍集中在 `GameContextTests.cs``ArchitectureServicesTests.cs``RegistryInitializationHookBaseTests.cs` 等既有热点,不属于本轮 nitpick follow-up 新引入问题
- 下一步:
1. 提交本轮 `PR #298` nitpick follow-up 与 `ai-plan` 同步。
2. 回到 `GameContextTests.cs` / `ArchitectureServicesTests.cs``CS8766` warning reduction 主线。
## 2026-04-27 — RP-085
### 阶段:按 `$gframework-batch-boot 100` 并行消化 `GFramework.Core.Tests` 低风险 `MA0048`