From 3ecce110ed31fb7a081e7629550477398d514760 Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Sat, 28 Feb 2026 13:03:45 +0800 Subject: [PATCH] =?UTF-8?q?feat(architecture):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E6=9E=B6=E6=9E=84=E4=B8=8A=E4=B8=8B=E6=96=87=E6=8F=90=E4=BE=9B?= =?UTF-8?q?=E8=80=85=E5=92=8C=E7=9B=B8=E5=85=B3=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 IArchitectureContextProvider 接口定义 - 实现 GameContextProvider 类提供上下文获取功能 - 添加 GameContext 静态类用于获取架构上下文 - 创建 ContextProviderTests 测试上下文提供者功能 - 实现 RegistryInitializationHookBase 抽象基类的完整测试 - 修改 IArchitectureContext.GetUtility 方法为虚拟方法以支持重写 --- .../architecture/ContextProviderTests.cs | 157 ++++++++ .../architecture/GameContextTests.cs | 2 +- .../RegistryInitializationHookBaseTests.cs | 367 ++++++++++++++++++ .../ContextAwareGeneratorSnapshotTests.cs | 41 +- 4 files changed, 551 insertions(+), 16 deletions(-) create mode 100644 GFramework.Core.Tests/architecture/ContextProviderTests.cs create mode 100644 GFramework.Core.Tests/architecture/RegistryInitializationHookBaseTests.cs diff --git a/GFramework.Core.Tests/architecture/ContextProviderTests.cs b/GFramework.Core.Tests/architecture/ContextProviderTests.cs new file mode 100644 index 0000000..d1358a7 --- /dev/null +++ b/GFramework.Core.Tests/architecture/ContextProviderTests.cs @@ -0,0 +1,157 @@ +using GFramework.Core.Abstractions.architecture; +using GFramework.Core.architecture; +using NUnit.Framework; + +namespace GFramework.Core.Tests.architecture; + +/// +/// ContextProvider 相关类的单元测试 +/// 测试内容包括: +/// - GameContextProvider 获取第一个架构上下文 +/// - GameContextProvider 尝试获取指定类型的上下文 +/// - ScopedContextProvider 获取绑定的上下文 +/// - ScopedContextProvider 尝试获取指定类型的上下文 +/// - ScopedContextProvider 类型不匹配时返回 false +/// +[TestFixture] +public class ContextProviderTests +{ + /// + /// 测试初始化方法,在每个测试方法执行前清空 GameContext + /// + [SetUp] + public void SetUp() + { + GameContext.Clear(); + } + + /// + /// 测试清理方法,在每个测试方法执行后清空 GameContext + /// + [TearDown] + public void TearDown() + { + GameContext.Clear(); + } + + /// + /// 测试 GameContextProvider 是否能正确获取第一个架构上下文 + /// + [Test] + public void GameContextProvider_GetContext_Should_Return_First_Context() + { + var context = new TestArchitectureContext(); + GameContext.Bind(typeof(TestArchitecture), context); + + var provider = new GameContextProvider(); + var result = provider.GetContext(); + + Assert.That(result, Is.SameAs(context)); + } + + /// + /// 测试 GameContextProvider 在没有上下文时是否抛出异常 + /// + [Test] + public void GameContextProvider_GetContext_Should_Throw_When_Empty() + { + var provider = new GameContextProvider(); + + Assert.Throws(() => provider.GetContext()); + } + + /// + /// 测试 GameContextProvider 的 TryGetContext 方法在找到上下文时返回 true + /// + [Test] + public void GameContextProvider_TryGetContext_Should_Return_True_When_Found() + { + var context = new TestArchitectureContext(); + GameContext.Bind(typeof(TestArchitectureContext), context); + + var provider = new GameContextProvider(); + var result = provider.TryGetContext(out var foundContext); + + Assert.That(result, Is.True); + Assert.That(foundContext, Is.SameAs(context)); + } + + /// + /// 测试 GameContextProvider 的 TryGetContext 方法在未找到上下文时返回 false + /// + [Test] + public void GameContextProvider_TryGetContext_Should_Return_False_When_Not_Found() + { + var provider = new GameContextProvider(); + var result = provider.TryGetContext(out var foundContext); + + Assert.That(result, Is.False); + Assert.That(foundContext, Is.Null); + } + + /// + /// 测试 ScopedContextProvider 是否能正确返回绑定的上下文 + /// + [Test] + public void ScopedContextProvider_GetContext_Should_Return_Bound_Context() + { + var context = new TestArchitectureContext(); + var provider = new ScopedContextProvider(context); + + var result = provider.GetContext(); + + Assert.That(result, Is.SameAs(context)); + } + + /// + /// 测试 ScopedContextProvider 的 TryGetContext 方法在类型匹配时返回 true + /// + [Test] + public void ScopedContextProvider_TryGetContext_Should_Return_True_When_Type_Matches() + { + var context = new TestArchitectureContext(); + var provider = new ScopedContextProvider(context); + + var result = provider.TryGetContext(out var foundContext); + + Assert.That(result, Is.True); + Assert.That(foundContext, Is.SameAs(context)); + } + + /// + /// 测试 ScopedContextProvider 的 TryGetContext 方法在类型不匹配时返回 false + /// + [Test] + public void ScopedContextProvider_TryGetContext_Should_Return_False_When_Type_Does_Not_Match() + { + var context = new TestArchitectureContext(); + var provider = new ScopedContextProvider(context); + + var result = provider.TryGetContext(out var foundContext); + + Assert.That(result, Is.False); + Assert.That(foundContext, Is.Null); + } + + /// + /// 测试 ScopedContextProvider 的 TryGetContext 方法支持接口类型查询 + /// + [Test] + public void ScopedContextProvider_TryGetContext_Should_Support_Interface_Type() + { + var context = new TestArchitectureContext(); + var provider = new ScopedContextProvider(context); + + var result = provider.TryGetContext(out var foundContext); + + Assert.That(result, Is.True); + Assert.That(foundContext, Is.SameAs(context)); + } +} + +/// +/// 另一个测试用的架构上下文类,用于测试类型不匹配的情况 +/// +public class AnotherTestArchitectureContext : TestArchitectureContext +{ +} \ No newline at end of file diff --git a/GFramework.Core.Tests/architecture/GameContextTests.cs b/GFramework.Core.Tests/architecture/GameContextTests.cs index 88b3dc7..9db9d72 100644 --- a/GFramework.Core.Tests/architecture/GameContextTests.cs +++ b/GFramework.Core.Tests/architecture/GameContextTests.cs @@ -294,7 +294,7 @@ public class TestArchitectureContext : IArchitectureContext /// /// 工具类型 /// 工具实例或null - public TUtility? GetUtility() where TUtility : class, IUtility + public virtual TUtility? GetUtility() where TUtility : class, IUtility { return _container.Get(); } diff --git a/GFramework.Core.Tests/architecture/RegistryInitializationHookBaseTests.cs b/GFramework.Core.Tests/architecture/RegistryInitializationHookBaseTests.cs new file mode 100644 index 0000000..54515d2 --- /dev/null +++ b/GFramework.Core.Tests/architecture/RegistryInitializationHookBaseTests.cs @@ -0,0 +1,367 @@ +using GFramework.Core.Abstractions.architecture; +using GFramework.Core.Abstractions.enums; +using GFramework.Core.Abstractions.lifecycle; +using GFramework.Core.Abstractions.model; +using GFramework.Core.Abstractions.system; +using GFramework.Core.Abstractions.utility; +using GFramework.Core.architecture; +using Microsoft.Extensions.DependencyInjection; +using NUnit.Framework; + +namespace GFramework.Core.Tests.architecture; + +/// +/// RegistryInitializationHookBase 抽象基类的单元测试 +/// 测试内容包括: +/// - 在目标阶段正确触发配置注册 +/// - 在非目标阶段不触发配置注册 +/// - 正确遍历所有配置项 +/// - 注册表不存在时不抛出异常 +/// - 支持自定义目标阶段 +/// +[TestFixture] +public class RegistryInitializationHookBaseTests +{ + /// + /// 测试在目标阶段时是否正确触发配置注册 + /// + [Test] + public void OnPhase_Should_Register_Configs_At_Target_Phase() + { + var registry = new TestRegistry(); + var configs = new[] { "config1", "config2", "config3" }; + var hook = new TestRegistryInitializationHook(configs); + var architecture = new TestArchitectureWithRegistry(registry); + + hook.OnPhase(ArchitecturePhase.AfterSystemInit, architecture); + + Assert.That(registry.RegisteredConfigs.Count, Is.EqualTo(3)); + Assert.That(registry.RegisteredConfigs, Is.EquivalentTo(configs)); + } + + /// + /// 测试在非目标阶段时不触发配置注册 + /// + [Test] + public void OnPhase_Should_Not_Register_Configs_At_Wrong_Phase() + { + var registry = new TestRegistry(); + var configs = new[] { "config1", "config2" }; + var hook = new TestRegistryInitializationHook(configs); + var architecture = new TestArchitectureWithRegistry(registry); + + hook.OnPhase(ArchitecturePhase.BeforeSystemInit, architecture); + + Assert.That(registry.RegisteredConfigs.Count, Is.EqualTo(0)); + } + + /// + /// 测试支持自定义目标阶段 + /// + [Test] + public void OnPhase_Should_Support_Custom_Target_Phase() + { + var registry = new TestRegistry(); + var configs = new[] { "config1" }; + var hook = new TestRegistryInitializationHook(configs, ArchitecturePhase.AfterModelInit); + var architecture = new TestArchitectureWithRegistry(registry); + + hook.OnPhase(ArchitecturePhase.AfterModelInit, architecture); + + Assert.That(registry.RegisteredConfigs.Count, Is.EqualTo(1)); + } + + /// + /// 测试当注册表不存在时不抛出异常 + /// + [Test] + public void OnPhase_Should_Not_Throw_When_Registry_Not_Found() + { + var configs = new[] { "config1" }; + var hook = new TestRegistryInitializationHook(configs); + var architecture = new TestArchitectureWithoutRegistry(); + + Assert.DoesNotThrow(() => hook.OnPhase(ArchitecturePhase.AfterSystemInit, architecture)); + } + + /// + /// 测试空配置集合不会导致错误 + /// + [Test] + public void OnPhase_Should_Handle_Empty_Configs() + { + var registry = new TestRegistry(); + var configs = Array.Empty(); + var hook = new TestRegistryInitializationHook(configs); + var architecture = new TestArchitectureWithRegistry(registry); + + hook.OnPhase(ArchitecturePhase.AfterSystemInit, architecture); + + Assert.That(registry.RegisteredConfigs.Count, Is.EqualTo(0)); + } + + /// + /// 测试多次调用同一阶段会重复注册 + /// + [Test] + public void OnPhase_Should_Register_Multiple_Times_If_Called_Multiple_Times() + { + var registry = new TestRegistry(); + var configs = new[] { "config1" }; + var hook = new TestRegistryInitializationHook(configs); + var architecture = new TestArchitectureWithRegistry(registry); + + hook.OnPhase(ArchitecturePhase.AfterSystemInit, architecture); + hook.OnPhase(ArchitecturePhase.AfterSystemInit, architecture); + + Assert.That(registry.RegisteredConfigs.Count, Is.EqualTo(2)); + } +} + +/// +/// 测试用的注册表初始化钩子实现 +/// +public class TestRegistryInitializationHook : RegistryInitializationHookBase +{ + public TestRegistryInitializationHook( + IEnumerable configs, + ArchitecturePhase targetPhase = ArchitecturePhase.AfterSystemInit) + : base(configs, targetPhase) + { + } + + protected override void RegisterConfig(TestRegistry registry, string config) + { + registry.Register(config); + } +} + +/// +/// 测试用的注册表类 +/// +public class TestRegistry : IUtility +{ + public List RegisteredConfigs { get; } = new(); + + public void Register(string config) + { + RegisteredConfigs.Add(config); + } +} + +/// +/// 测试用的架构类(包含注册表) +/// +public class TestArchitectureWithRegistry : IArchitecture +{ + private readonly TestRegistry _registry; + + public TestArchitectureWithRegistry(TestRegistry registry) + { + _registry = registry; + Context = new TestArchitectureContextWithRegistry(registry); + } + + public Action? Configurator { get; } + + public IArchitectureContext Context { get; } + Action? IArchitecture.Configurator => Configurator; + + T IArchitecture.RegisterSystem(T system) + { + throw new NotImplementedException(); + } + + T IArchitecture.RegisterModel(T model) + { + throw new NotImplementedException(); + } + + T IArchitecture.RegisterUtility(T utility) + { + throw new NotImplementedException(); + } + + public void RegisterMediatorBehavior() where TBehavior : class + { + throw new NotImplementedException(); + } + + public IArchitectureModule InstallModule(IArchitectureModule module) + { + throw new NotImplementedException(); + } + + IArchitectureLifecycleHook IArchitecture.RegisterLifecycleHook(IArchitectureLifecycleHook hook) + { + throw new NotImplementedException(); + } + + Task IArchitecture.WaitUntilReadyAsync() + { + return WaitUntilReadyAsync(); + } + + public void RegisterUtility(Action? onCreated = default(Action?)) where T : class, IUtility + { + throw new NotImplementedException(); + } + + public void RegisterModel(Action? onCreated = default(Action?)) where T : class, IModel + { + throw new NotImplementedException(); + } + + public void RegisterSystem(Action? onCreated = default(Action?)) where T : class, ISystem + { + throw new NotImplementedException(); + } + + public void Initialize() + { + } + + public void Destroy() + { + throw new NotImplementedException(); + } + + Task IAsyncInitializable.InitializeAsync() + { + return InitializeAsync(); + } + + ValueTask IAsyncDestroyable.DestroyAsync() + { + return DestroyAsync(); + } + + public Task WaitUntilReadyAsync() + { + throw new NotImplementedException(); + } + + public void RegisterLifecycleHook(IArchitectureLifecycleHook hook) + { + } + + public Task InitializeAsync() + { + throw new NotImplementedException(); + } + + public ValueTask DestroyAsync() + { + throw new NotImplementedException(); + } +} + +/// +/// 测试用的架构上下文类(包含注册表) +/// +public class TestArchitectureContextWithRegistry : TestArchitectureContext +{ + private readonly TestRegistry _registry; + + public TestArchitectureContextWithRegistry(TestRegistry registry) + { + _registry = registry; + } + + public override TUtility GetUtility() + { + if (typeof(TUtility) == typeof(TestRegistry)) + { + return _registry as TUtility; + } + + return base.GetUtility(); + } +} + +/// +/// 测试用的架构类(不包含注册表) +/// +public class TestArchitectureWithoutRegistry : IArchitecture +{ + public TestArchitectureWithoutRegistry() + { + Context = new TestArchitectureContext(); + } + + public IArchitectureContext Context { get; } + public Action? Configurator { get; } + + T IArchitecture.RegisterSystem(T system) + { + throw new NotImplementedException(); + } + + T IArchitecture.RegisterModel(T model) + { + throw new NotImplementedException(); + } + + T IArchitecture.RegisterUtility(T utility) + { + throw new NotImplementedException(); + } + + public void RegisterMediatorBehavior() where TBehavior : class + { + throw new NotImplementedException(); + } + + public IArchitectureModule InstallModule(IArchitectureModule module) + { + throw new NotImplementedException(); + } + + IArchitectureLifecycleHook IArchitecture.RegisterLifecycleHook(IArchitectureLifecycleHook hook) + { + throw new NotImplementedException(); + } + + public Task WaitUntilReadyAsync() + { + throw new NotImplementedException(); + } + + public void RegisterUtility(Action? onCreated = default(Action?)) where T : class, IUtility + { + throw new NotImplementedException(); + } + + public void RegisterModel(Action? onCreated = default(Action?)) where T : class, IModel + { + throw new NotImplementedException(); + } + + public void RegisterSystem(Action? onCreated = default(Action?)) where T : class, ISystem + { + throw new NotImplementedException(); + } + + public void Initialize() + { + } + + public Task InitializeAsync() + { + throw new NotImplementedException(); + } + + public ValueTask DestroyAsync() + { + throw new NotImplementedException(); + } + + public void Destroy() + { + throw new NotImplementedException(); + } + + public void RegisterLifecycleHook(IArchitectureLifecycleHook hook) + { + } +} \ No newline at end of file diff --git a/GFramework.SourceGenerators.Tests/rule/ContextAwareGeneratorSnapshotTests.cs b/GFramework.SourceGenerators.Tests/rule/ContextAwareGeneratorSnapshotTests.cs index 38a15a6..a91a721 100644 --- a/GFramework.SourceGenerators.Tests/rule/ContextAwareGeneratorSnapshotTests.cs +++ b/GFramework.SourceGenerators.Tests/rule/ContextAwareGeneratorSnapshotTests.cs @@ -44,6 +44,32 @@ public class ContextAwareGeneratorSnapshotTests namespace GFramework.Core.Abstractions.architecture { public interface IArchitectureContext { } + + public interface IArchitectureContextProvider + { + IArchitectureContext GetContext(); + bool TryGetContext(out T? context) where T : class, IArchitectureContext; + } + } + + namespace GFramework.Core.architecture + { + using GFramework.Core.Abstractions.architecture; + + public sealed class GameContextProvider : IArchitectureContextProvider + { + public IArchitectureContext GetContext() => null; + public bool TryGetContext(out T? context) where T : class, IArchitectureContext + { + context = null; + return false; + } + } + + public static class GameContext + { + public static IArchitectureContext GetFirstArchitectureContext() => null; + } } namespace TestApp @@ -56,21 +82,6 @@ public class ContextAwareGeneratorSnapshotTests { } } - namespace GFramework.Core.architecture - { - using GFramework.Core.Abstractions.architecture; - public static class GameContext{ - /// - /// 获取字典中的第一个架构上下文 - /// - /// 返回字典中的第一个架构上下文实例 - /// 当字典为空时抛出 - public static IArchitectureContext GetFirstArchitectureContext() - { - return null; - } - } - } """; // 执行生成器快照测试,将生成的代码与预期快照进行比较