feat(architecture): 添加架构上下文提供者和相关测试

- 新增 IArchitectureContextProvider 接口定义
- 实现 GameContextProvider 类提供上下文获取功能
- 添加 GameContext 静态类用于获取架构上下文
- 创建 ContextProviderTests 测试上下文提供者功能
- 实现 RegistryInitializationHookBase 抽象基类的完整测试
- 修改 IArchitectureContext.GetUtility 方法为虚拟方法以支持重写
This commit is contained in:
GeWuYou 2026-02-28 13:03:45 +08:00 committed by gewuyou
parent 0ed8edf015
commit 3ecce110ed
4 changed files with 551 additions and 16 deletions

View File

@ -0,0 +1,157 @@
using GFramework.Core.Abstractions.architecture;
using GFramework.Core.architecture;
using NUnit.Framework;
namespace GFramework.Core.Tests.architecture;
/// <summary>
/// ContextProvider 相关类的单元测试
/// 测试内容包括:
/// - GameContextProvider 获取第一个架构上下文
/// - GameContextProvider 尝试获取指定类型的上下文
/// - ScopedContextProvider 获取绑定的上下文
/// - ScopedContextProvider 尝试获取指定类型的上下文
/// - ScopedContextProvider 类型不匹配时返回 false
/// </summary>
[TestFixture]
public class ContextProviderTests
{
/// <summary>
/// 测试初始化方法,在每个测试方法执行前清空 GameContext
/// </summary>
[SetUp]
public void SetUp()
{
GameContext.Clear();
}
/// <summary>
/// 测试清理方法,在每个测试方法执行后清空 GameContext
/// </summary>
[TearDown]
public void TearDown()
{
GameContext.Clear();
}
/// <summary>
/// 测试 GameContextProvider 是否能正确获取第一个架构上下文
/// </summary>
[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));
}
/// <summary>
/// 测试 GameContextProvider 在没有上下文时是否抛出异常
/// </summary>
[Test]
public void GameContextProvider_GetContext_Should_Throw_When_Empty()
{
var provider = new GameContextProvider();
Assert.Throws<InvalidOperationException>(() => provider.GetContext());
}
/// <summary>
/// 测试 GameContextProvider 的 TryGetContext 方法在找到上下文时返回 true
/// </summary>
[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<TestArchitectureContext>(out var foundContext);
Assert.That(result, Is.True);
Assert.That(foundContext, Is.SameAs(context));
}
/// <summary>
/// 测试 GameContextProvider 的 TryGetContext 方法在未找到上下文时返回 false
/// </summary>
[Test]
public void GameContextProvider_TryGetContext_Should_Return_False_When_Not_Found()
{
var provider = new GameContextProvider();
var result = provider.TryGetContext<TestArchitectureContext>(out var foundContext);
Assert.That(result, Is.False);
Assert.That(foundContext, Is.Null);
}
/// <summary>
/// 测试 ScopedContextProvider 是否能正确返回绑定的上下文
/// </summary>
[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));
}
/// <summary>
/// 测试 ScopedContextProvider 的 TryGetContext 方法在类型匹配时返回 true
/// </summary>
[Test]
public void ScopedContextProvider_TryGetContext_Should_Return_True_When_Type_Matches()
{
var context = new TestArchitectureContext();
var provider = new ScopedContextProvider(context);
var result = provider.TryGetContext<TestArchitectureContext>(out var foundContext);
Assert.That(result, Is.True);
Assert.That(foundContext, Is.SameAs(context));
}
/// <summary>
/// 测试 ScopedContextProvider 的 TryGetContext 方法在类型不匹配时返回 false
/// </summary>
[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<AnotherTestArchitectureContext>(out var foundContext);
Assert.That(result, Is.False);
Assert.That(foundContext, Is.Null);
}
/// <summary>
/// 测试 ScopedContextProvider 的 TryGetContext 方法支持接口类型查询
/// </summary>
[Test]
public void ScopedContextProvider_TryGetContext_Should_Support_Interface_Type()
{
var context = new TestArchitectureContext();
var provider = new ScopedContextProvider(context);
var result = provider.TryGetContext<IArchitectureContext>(out var foundContext);
Assert.That(result, Is.True);
Assert.That(foundContext, Is.SameAs(context));
}
}
/// <summary>
/// 另一个测试用的架构上下文类,用于测试类型不匹配的情况
/// </summary>
public class AnotherTestArchitectureContext : TestArchitectureContext
{
}

View File

@ -294,7 +294,7 @@ public class TestArchitectureContext : IArchitectureContext
/// </summary>
/// <typeparam name="TUtility">工具类型</typeparam>
/// <returns>工具实例或null</returns>
public TUtility? GetUtility<TUtility>() where TUtility : class, IUtility
public virtual TUtility? GetUtility<TUtility>() where TUtility : class, IUtility
{
return _container.Get<TUtility>();
}

View File

@ -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;
/// <summary>
/// RegistryInitializationHookBase 抽象基类的单元测试
/// 测试内容包括:
/// - 在目标阶段正确触发配置注册
/// - 在非目标阶段不触发配置注册
/// - 正确遍历所有配置项
/// - 注册表不存在时不抛出异常
/// - 支持自定义目标阶段
/// </summary>
[TestFixture]
public class RegistryInitializationHookBaseTests
{
/// <summary>
/// 测试在目标阶段时是否正确触发配置注册
/// </summary>
[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));
}
/// <summary>
/// 测试在非目标阶段时不触发配置注册
/// </summary>
[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));
}
/// <summary>
/// 测试支持自定义目标阶段
/// </summary>
[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));
}
/// <summary>
/// 测试当注册表不存在时不抛出异常
/// </summary>
[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));
}
/// <summary>
/// 测试空配置集合不会导致错误
/// </summary>
[Test]
public void OnPhase_Should_Handle_Empty_Configs()
{
var registry = new TestRegistry();
var configs = Array.Empty<string>();
var hook = new TestRegistryInitializationHook(configs);
var architecture = new TestArchitectureWithRegistry(registry);
hook.OnPhase(ArchitecturePhase.AfterSystemInit, architecture);
Assert.That(registry.RegisteredConfigs.Count, Is.EqualTo(0));
}
/// <summary>
/// 测试多次调用同一阶段会重复注册
/// </summary>
[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));
}
}
/// <summary>
/// 测试用的注册表初始化钩子实现
/// </summary>
public class TestRegistryInitializationHook : RegistryInitializationHookBase<TestRegistry, string>
{
public TestRegistryInitializationHook(
IEnumerable<string> configs,
ArchitecturePhase targetPhase = ArchitecturePhase.AfterSystemInit)
: base(configs, targetPhase)
{
}
protected override void RegisterConfig(TestRegistry registry, string config)
{
registry.Register(config);
}
}
/// <summary>
/// 测试用的注册表类
/// </summary>
public class TestRegistry : IUtility
{
public List<string> RegisteredConfigs { get; } = new();
public void Register(string config)
{
RegisteredConfigs.Add(config);
}
}
/// <summary>
/// 测试用的架构类(包含注册表)
/// </summary>
public class TestArchitectureWithRegistry : IArchitecture
{
private readonly TestRegistry _registry;
public TestArchitectureWithRegistry(TestRegistry registry)
{
_registry = registry;
Context = new TestArchitectureContextWithRegistry(registry);
}
public Action<IServiceCollection>? Configurator { get; }
public IArchitectureContext Context { get; }
Action<IServiceCollection>? IArchitecture.Configurator => Configurator;
T IArchitecture.RegisterSystem<T>(T system)
{
throw new NotImplementedException();
}
T IArchitecture.RegisterModel<T>(T model)
{
throw new NotImplementedException();
}
T IArchitecture.RegisterUtility<T>(T utility)
{
throw new NotImplementedException();
}
public void RegisterMediatorBehavior<TBehavior>() 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<T>(Action<T>? onCreated = default(Action<T>?)) where T : class, IUtility
{
throw new NotImplementedException();
}
public void RegisterModel<T>(Action<T>? onCreated = default(Action<T>?)) where T : class, IModel
{
throw new NotImplementedException();
}
public void RegisterSystem<T>(Action<T>? onCreated = default(Action<T>?)) 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();
}
}
/// <summary>
/// 测试用的架构上下文类(包含注册表)
/// </summary>
public class TestArchitectureContextWithRegistry : TestArchitectureContext
{
private readonly TestRegistry _registry;
public TestArchitectureContextWithRegistry(TestRegistry registry)
{
_registry = registry;
}
public override TUtility GetUtility<TUtility>()
{
if (typeof(TUtility) == typeof(TestRegistry))
{
return _registry as TUtility;
}
return base.GetUtility<TUtility>();
}
}
/// <summary>
/// 测试用的架构类(不包含注册表)
/// </summary>
public class TestArchitectureWithoutRegistry : IArchitecture
{
public TestArchitectureWithoutRegistry()
{
Context = new TestArchitectureContext();
}
public IArchitectureContext Context { get; }
public Action<IServiceCollection>? Configurator { get; }
T IArchitecture.RegisterSystem<T>(T system)
{
throw new NotImplementedException();
}
T IArchitecture.RegisterModel<T>(T model)
{
throw new NotImplementedException();
}
T IArchitecture.RegisterUtility<T>(T utility)
{
throw new NotImplementedException();
}
public void RegisterMediatorBehavior<TBehavior>() 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<T>(Action<T>? onCreated = default(Action<T>?)) where T : class, IUtility
{
throw new NotImplementedException();
}
public void RegisterModel<T>(Action<T>? onCreated = default(Action<T>?)) where T : class, IModel
{
throw new NotImplementedException();
}
public void RegisterSystem<T>(Action<T>? onCreated = default(Action<T>?)) 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)
{
}
}

View File

@ -44,6 +44,32 @@ public class ContextAwareGeneratorSnapshotTests
namespace GFramework.Core.Abstractions.architecture
{
public interface IArchitectureContext { }
public interface IArchitectureContextProvider
{
IArchitectureContext GetContext();
bool TryGetContext<T>(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<T>(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{
/// <summary>
/// 获取字典中的第一个架构上下文
/// </summary>
/// <returns>返回字典中的第一个架构上下文实例</returns>
/// <exception cref="InvalidOperationException">当字典为空时抛出</exception>
public static IArchitectureContext GetFirstArchitectureContext()
{
return null;
}
}
}
""";
// 执行生成器快照测试,将生成的代码与预期快照进行比较