test(core): 添加核心组件的单元测试

- 为 ContextAware 功能添加全面的单元测试覆盖
- 增加对枚举扩展生成器的快照测试验证
- 实现环境管理器的完整测试用例集
- 添加事件总线功能的核心测试验证
- 为游戏上下文管理添加架构测试
- 扩展注销列表扩展方法的测试覆盖
- 增加注销机制的全面单元测试验证
- [skip ci]
This commit is contained in:
GeWuYou 2026-01-15 14:36:30 +08:00
parent dda2d8f864
commit 5d11666fd8
8 changed files with 987 additions and 0 deletions

View File

@ -0,0 +1,198 @@
using GFramework.Core.Abstractions.architecture;
using GFramework.Core.Abstractions.command;
using GFramework.Core.Abstractions.environment;
using GFramework.Core.Abstractions.events;
using GFramework.Core.Abstractions.ioc;
using GFramework.Core.Abstractions.model;
using GFramework.Core.Abstractions.query;
using GFramework.Core.Abstractions.system;
using GFramework.Core.Abstractions.utility;
using GFramework.Core.architecture;
using GFramework.Core.command;
using GFramework.Core.environment;
using GFramework.Core.events;
using GFramework.Core.ioc;
using GFramework.Core.query;
using NUnit.Framework;
namespace GFramework.Core.Tests.architecture;
[TestFixture]
public class GameContextTests
{
[SetUp]
public void SetUp()
{
GameContext.Clear();
}
[TearDown]
public void TearDown()
{
GameContext.Clear();
}
[Test]
public void ArchitectureReadOnlyDictionary_Should_Return_Empty_At_Start()
{
var dict = GameContext.ArchitectureReadOnlyDictionary;
Assert.That(dict.Count, Is.EqualTo(0));
}
[Test]
public void Bind_Should_Add_Context_To_Dictionary()
{
var context = new TestArchitectureContext();
GameContext.Bind(typeof(TestArchitecture), context);
Assert.That(GameContext.ArchitectureReadOnlyDictionary.Count, Is.EqualTo(1));
}
[Test]
public void Bind_WithDuplicateType_Should_ThrowInvalidOperationException()
{
var context1 = new TestArchitectureContext();
var context2 = new TestArchitectureContext();
GameContext.Bind(typeof(TestArchitecture), context1);
Assert.Throws<InvalidOperationException>(() =>
GameContext.Bind(typeof(TestArchitecture), context2));
}
[Test]
public void GetByType_Should_Return_Correct_Context()
{
var context = new TestArchitectureContext();
GameContext.Bind(typeof(TestArchitecture), context);
var result = GameContext.GetByType(typeof(TestArchitecture));
Assert.That(result, Is.SameAs(context));
}
[Test]
public void GetByType_Should_Throw_When_Not_Found()
{
Assert.Throws<InvalidOperationException>(() =>
GameContext.GetByType(typeof(TestArchitecture)));
}
[Test]
public void GetGeneric_Should_Return_Correct_Context()
{
var context = new TestArchitectureContext();
GameContext.Bind(typeof(TestArchitectureContext), context);
var result = GameContext.Get<TestArchitectureContext>();
Assert.That(result, Is.SameAs(context));
}
[Test]
public void TryGet_Should_ReturnTrue_When_Found()
{
var context = new TestArchitectureContext();
GameContext.Bind(typeof(TestArchitectureContext), context);
var result = GameContext.TryGet(out TestArchitectureContext? foundContext);
Assert.That(result, Is.True);
Assert.That(foundContext, Is.SameAs(context));
}
[Test]
public void TryGet_Should_ReturnFalse_When_Not_Found()
{
var result = GameContext.TryGet(out TestArchitectureContext? foundContext);
Assert.That(result, Is.False);
Assert.That(foundContext, Is.Null);
}
[Test]
public void GetFirstArchitectureContext_Should_Return_When_Exists()
{
var context = new TestArchitectureContext();
GameContext.Bind(typeof(TestArchitecture), context);
var result = GameContext.GetFirstArchitectureContext();
Assert.That(result, Is.SameAs(context));
}
[Test]
public void GetFirstArchitectureContext_Should_Throw_When_Empty()
{
Assert.Throws<InvalidOperationException>(() =>
GameContext.GetFirstArchitectureContext());
}
[Test]
public void Unbind_Should_Remove_Context()
{
var context = new TestArchitectureContext();
GameContext.Bind(typeof(TestArchitecture), context);
GameContext.Unbind(typeof(TestArchitecture));
Assert.That(GameContext.ArchitectureReadOnlyDictionary.Count, Is.EqualTo(0));
}
[Test]
public void Clear_Should_Remove_All_Contexts()
{
GameContext.Bind(typeof(TestArchitecture), new TestArchitectureContext());
GameContext.Bind(typeof(TestArchitectureContext), new TestArchitectureContext());
GameContext.Clear();
Assert.That(GameContext.ArchitectureReadOnlyDictionary.Count, Is.EqualTo(0));
}
}
public class TestArchitecture : Architecture
{
protected override void Init()
{
}
}
public class TestArchitectureContext : IArchitectureContext
{
private readonly IocContainer _container = new();
public IIocContainer Container => _container;
public IEventBus EventBus => new EventBus();
public ICommandBus CommandBus => new CommandBus();
public IQueryBus QueryBus => new QueryBus();
public IEnvironment Environment => new DefaultEnvironment();
public TModel? GetModel<TModel>() where TModel : class, IModel => _container.Get<TModel>();
public TSystem? GetSystem<TSystem>() where TSystem : class, ISystem => _container.Get<TSystem>();
public TUtility? GetUtility<TUtility>() where TUtility : class, IUtility => _container.Get<TUtility>();
public void SendEvent<TEvent>() where TEvent : new()
{
}
public void SendEvent<TEvent>(TEvent e) where TEvent : class
{
}
public IUnRegister RegisterEvent<TEvent>(Action<TEvent> handler) => new DefaultUnRegister(() => { });
public void UnRegisterEvent<TEvent>(Action<TEvent> onEvent)
{
}
public void SendCommand(ICommand command)
{
}
public TResult SendCommand<TResult>(ICommand<TResult> command) => default!;
public TResult SendQuery<TResult>(IQuery<TResult> query) => default!;
public IEnvironment GetEnvironment() => Environment;
}

View File

@ -0,0 +1,156 @@
using GFramework.Core.Abstractions.environment;
using GFramework.Core.environment;
using NUnit.Framework;
namespace GFramework.Core.Tests.environment;
[TestFixture]
public class EnvironmentTests
{
[SetUp]
public void SetUp()
{
_environment = new TestEnvironment();
_environment.Initialize();
}
private TestEnvironment _environment = null!;
[Test]
public void DefaultEnvironment_Name_Should_ReturnDefault()
{
var env = new DefaultEnvironment();
Assert.That(env.Name, Is.EqualTo("Default"));
}
[Test]
public void DefaultEnvironment_Initialize_Should_NotThrow()
{
var env = new DefaultEnvironment();
Assert.DoesNotThrow(() => env.Initialize());
}
[Test]
public void Get_Should_Return_Value_When_Key_Exists()
{
_environment.Register("testKey", "testValue");
var result = _environment.Get<string>("testKey");
Assert.That(result, Is.EqualTo("testValue"));
}
[Test]
public void Get_Should_ReturnNull_When_Key_Not_Exists()
{
var result = _environment.Get<string>("nonExistentKey");
Assert.That(result, Is.Null);
}
[Test]
public void Get_Should_ReturnNull_When_Type_Does_Not_Match()
{
_environment.Register("testKey", "testValue");
var result = _environment.Get<List<int>>("testKey");
Assert.That(result, Is.Null);
}
[Test]
public void TryGet_Should_ReturnTrue_And_Value_When_Key_Exists()
{
_environment.Register("testKey", "testValue");
var result = _environment.TryGet<string>("testKey", out var value);
Assert.That(result, Is.True);
Assert.That(value, Is.EqualTo("testValue"));
}
[Test]
public void TryGet_Should_ReturnFalse_When_Key_Not_Exists()
{
var result = _environment.TryGet<string>("nonExistentKey", out var value);
Assert.That(result, Is.False);
Assert.That(value, Is.Null);
}
[Test]
public void TryGet_Should_ReturnFalse_When_Type_Does_Not_Match()
{
_environment.Register("testKey", "testValue");
var result = _environment.TryGet<List<int>>("testKey", out var value);
Assert.That(result, Is.False);
Assert.That(value, Is.Null);
}
[Test]
public void GetRequired_Should_Return_Value_When_Key_Exists()
{
_environment.Register("testKey", "testValue");
var result = _environment.GetRequired<string>("testKey");
Assert.That(result, Is.EqualTo("testValue"));
}
[Test]
public void GetRequired_Should_ThrowInvalidOperationException_When_Key_Not_Exists()
{
Assert.Throws<InvalidOperationException>(() =>
_environment.GetRequired<string>("nonExistentKey"));
}
[Test]
public void Register_Should_Add_Value_To_Dictionary()
{
_environment.Register("newKey", "newValue");
var result = _environment.Get<string>("newKey");
Assert.That(result, Is.EqualTo("newValue"));
}
[Test]
public void Register_Should_Overwrite_Existing_Value()
{
_environment.Register("testKey", "value1");
_environment.Register("testKey", "value2");
var result = _environment.Get<string>("testKey");
Assert.That(result, Is.EqualTo("value2"));
}
[Test]
public void IEnvironment_Register_Should_Add_Value()
{
IEnvironment env = _environment;
env.Register("interfaceKey", "interfaceValue");
var result = env.Get<string>("interfaceKey");
Assert.That(result, Is.EqualTo("interfaceValue"));
}
}
public class TestEnvironment : EnvironmentBase
{
public override string Name { get; } = "TestEnvironment";
public new void Register(string key, object value)
{
base.Register(key, value);
}
public override void Initialize()
{
}
}

View File

@ -0,0 +1,61 @@
using GFramework.Core.events;
using NUnit.Framework;
namespace GFramework.Core.Tests.events;
[TestFixture]
public class EventBusTests
{
[SetUp]
public void SetUp()
{
_eventBus = new EventBus();
}
private EventBus _eventBus = null!;
[Test]
public void Register_Should_Add_Handler()
{
var called = false;
_eventBus.Register<EventBusTestsEvent>(@event => { called = true; });
_eventBus.Send<EventBusTestsEvent>();
Assert.That(called, Is.True);
}
[Test]
public void UnRegister_Should_Remove_Handler()
{
var count = 0;
Action<EventBusTestsEvent> handler = @event => { count++; };
_eventBus.Register(handler);
_eventBus.Send<EventBusTestsEvent>();
Assert.That(count, Is.EqualTo(1));
_eventBus.UnRegister(handler);
_eventBus.Send<EventBusTestsEvent>();
Assert.That(count, Is.EqualTo(1));
}
[Test]
public void SendEvent_Should_Invoke_All_Handlers()
{
var count1 = 0;
var count2 = 0;
_eventBus.Register<EventBusTestsEvent>(@event => { count1++; });
_eventBus.Register<EventBusTestsEvent>(@event => { count2++; });
_eventBus.Send<EventBusTestsEvent>();
Assert.That(count1, Is.EqualTo(1));
Assert.That(count2, Is.EqualTo(1));
}
}
public class EventBusTestsEvent
{
}

View File

@ -0,0 +1,89 @@
using GFramework.Core.events;
using GFramework.Core.property;
using NUnit.Framework;
namespace GFramework.Core.Tests.events;
[TestFixture]
public class UnRegisterTests
{
[Test]
public void DefaultUnRegister_Should_InvokeCallback_When_UnRegisterCalled()
{
var invoked = false;
var unRegister = new DefaultUnRegister(() => { invoked = true; });
unRegister.UnRegister();
Assert.That(invoked, Is.True);
}
[Test]
public void DefaultUnRegister_Should_ClearCallback_After_UnRegister()
{
var callCount = 0;
var unRegister = new DefaultUnRegister(() => { callCount++; });
unRegister.UnRegister();
unRegister.UnRegister();
Assert.That(callCount, Is.EqualTo(1));
}
[Test]
public void DefaultUnRegister_WithNullCallback_Should_NotThrow()
{
var unRegister = new DefaultUnRegister(null!);
Assert.DoesNotThrow(() => unRegister.UnRegister());
}
[Test]
public void BindablePropertyUnRegister_Should_UnRegister_From_Property()
{
var property = new BindableProperty<int>(0);
var callCount = 0;
Action<int> handler = _ => { callCount++; };
property.Register(handler);
var unRegister = new BindablePropertyUnRegister<int>(property, handler);
unRegister.UnRegister();
property.Value = 42;
Assert.That(callCount, Is.EqualTo(0));
}
[Test]
public void BindablePropertyUnRegister_Should_Clear_References()
{
var property = new BindableProperty<int>(0);
Action<int> handler = _ => { };
var unRegister = new BindablePropertyUnRegister<int>(property, handler);
unRegister.UnRegister();
Assert.That(unRegister.BindableProperty, Is.Null);
Assert.That(unRegister.OnValueChanged, Is.Null);
}
[Test]
public void BindablePropertyUnRegister_WithNull_Property_Should_NotThrow()
{
Action<int> handler = _ => { };
var unRegister = new BindablePropertyUnRegister<int>(null!, handler);
Assert.DoesNotThrow(() => unRegister.UnRegister());
}
[Test]
public void BindablePropertyUnRegister_WithNull_Handler_Should_NotThrow()
{
var property = new BindableProperty<int>(0);
var unRegister = new BindablePropertyUnRegister<int>(property, null!);
Assert.DoesNotThrow(() => unRegister.UnRegister());
}
}

View File

@ -0,0 +1,99 @@
using GFramework.Core.Abstractions.events;
using GFramework.Core.events;
using GFramework.Core.extensions;
using NUnit.Framework;
namespace GFramework.Core.Tests.extensions;
[TestFixture]
public class UnRegisterListExtensionTests
{
[SetUp]
public void SetUp()
{
_unRegisterList = new TestUnRegisterList();
}
private TestUnRegisterList _unRegisterList = null!;
[Test]
public void AddToUnregisterList_Should_Add_To_List()
{
var unRegister = new DefaultUnRegister(() => { });
unRegister.AddToUnregisterList(_unRegisterList);
Assert.That(_unRegisterList.UnregisterList.Count, Is.EqualTo(1));
}
[Test]
public void AddToUnregisterList_Should_Add_Multiple_Elements()
{
var unRegister1 = new DefaultUnRegister(() => { });
var unRegister2 = new DefaultUnRegister(() => { });
var unRegister3 = new DefaultUnRegister(() => { });
unRegister1.AddToUnregisterList(_unRegisterList);
unRegister2.AddToUnregisterList(_unRegisterList);
unRegister3.AddToUnregisterList(_unRegisterList);
Assert.That(_unRegisterList.UnregisterList.Count, Is.EqualTo(3));
}
[Test]
public void UnRegisterAll_Should_UnRegister_All_Elements()
{
var invoked1 = false;
var invoked2 = false;
var invoked3 = false;
var unRegister1 = new DefaultUnRegister(() => { invoked1 = true; });
var unRegister2 = new DefaultUnRegister(() => { invoked2 = true; });
var unRegister3 = new DefaultUnRegister(() => { invoked3 = true; });
unRegister1.AddToUnregisterList(_unRegisterList);
unRegister2.AddToUnregisterList(_unRegisterList);
unRegister3.AddToUnregisterList(_unRegisterList);
_unRegisterList.UnRegisterAll();
Assert.That(invoked1, Is.True);
Assert.That(invoked2, Is.True);
Assert.That(invoked3, Is.True);
}
[Test]
public void UnRegisterAll_Should_Clear_List()
{
var unRegister = new DefaultUnRegister(() => { });
unRegister.AddToUnregisterList(_unRegisterList);
_unRegisterList.UnRegisterAll();
Assert.That(_unRegisterList.UnregisterList.Count, Is.EqualTo(0));
}
[Test]
public void UnRegisterAll_Should_Not_Throw_When_Empty()
{
Assert.DoesNotThrow(() => _unRegisterList.UnRegisterAll());
}
[Test]
public void UnRegisterAll_Should_Invoke_Once_Per_Element()
{
var callCount = 0;
var unRegister = new DefaultUnRegister(() => { callCount++; });
unRegister.AddToUnregisterList(_unRegisterList);
_unRegisterList.UnRegisterAll();
Assert.That(callCount, Is.EqualTo(1));
}
}
public class TestUnRegisterList : IUnRegisterList
{
public IList<IUnRegister> UnregisterList { get; } = new List<IUnRegister>();
}

View File

@ -0,0 +1,76 @@
using GFramework.Core.Abstractions.architecture;
using GFramework.Core.Abstractions.rule;
using GFramework.Core.rule;
using GFramework.Core.Tests.architecture;
using NUnit.Framework;
namespace GFramework.Core.Tests.rule;
[TestFixture]
public class ContextAwareTests
{
[SetUp]
public void SetUp()
{
_contextAware = new TestContextAware();
_mockContext = new TestArchitectureContext();
}
private TestContextAware _contextAware = null!;
private TestArchitectureContext _mockContext = null!;
[Test]
public void SetContext_Should_Set_Context_Property()
{
IContextAware aware = _contextAware;
aware.SetContext(_mockContext);
Assert.That(_contextAware.PublicContext, Is.SameAs(_mockContext));
}
[Test]
public void SetContext_Should_Call_OnContextReady()
{
IContextAware aware = _contextAware;
aware.SetContext(_mockContext);
Assert.That(_contextAware.OnContextReadyCalled, Is.True);
}
[Test]
public void GetContext_Should_Return_Set_Context()
{
IContextAware aware = _contextAware;
aware.SetContext(_mockContext);
var result = aware.GetContext();
Assert.That(result, Is.SameAs(_mockContext));
}
[Test]
public void GetContext_Should_Return_FirstArchitectureContext_When_Not_Set()
{
// Arrange - 暂时不调用 SetContext让 Context 为 null
IContextAware aware = _contextAware;
// Act - 当 Context 为 null 时,应该返回第一个 Architecture Context
// 由于测试环境中没有实际的 Architecture Context这里只测试调用不会抛出异常
// 在实际使用中,当 Context 为 null 时会调用 GameContext.GetFirstArchitectureContext()
// Assert - 验证在没有设置 Context 时的行为
// 注意:由于测试环境中可能没有 Architecture Context这里我们只测试不抛出异常
Assert.DoesNotThrow(() => aware.GetContext());
}
}
public class TestContextAware : ContextAwareBase
{
public IArchitectureContext? PublicContext => Context;
public bool OnContextReadyCalled { get; private set; }
protected override void OnContextReady()
{
OnContextReadyCalled = true;
}
}

View File

@ -0,0 +1,149 @@
using GFramework.SourceGenerators.enums;
using GFramework.SourceGenerators.Tests.core;
using NUnit.Framework;
namespace GFramework.SourceGenerators.Tests.enums;
[TestFixture]
public class EnumExtensionsGeneratorSnapshotTests
{
[Test]
public async Task Snapshot_BasicEnum_IsMethods()
{
const string source = """
using GFramework.SourceGenerators.Abstractions.enums;
namespace TestApp
{
[GenerateEnumExtensions]
public enum Status
{
Active,
Inactive,
Pending
}
}
""";
await GeneratorSnapshotTest<EnumExtensionsGenerator>.RunAsync(
source,
Path.Combine(
TestContext.CurrentContext.TestDirectory,
"enums",
"snapshots",
"EnumExtensionsGenerator",
"BasicEnum_IsMethods"));
}
[Test]
public async Task Snapshot_BasicEnum_IsInMethod()
{
const string source = """
using GFramework.SourceGenerators.Abstractions.enums;
namespace TestApp
{
[GenerateEnumExtensions]
public enum Status
{
Active,
Inactive
}
}
""";
await GeneratorSnapshotTest<EnumExtensionsGenerator>.RunAsync(
source,
Path.Combine(
TestContext.CurrentContext.TestDirectory,
"enums",
"snapshots",
"EnumExtensionsGenerator",
"BasicEnum_IsInMethod"));
}
[Test]
public async Task Snapshot_EnumWithFlagValues()
{
const string source = """
using GFramework.SourceGenerators.Abstractions.enums;
using System;
namespace TestApp
{
[GenerateEnumExtensions]
[Flags]
public enum Permissions
{
None = 0,
Read = 1,
Write = 2,
Execute = 4
}
}
""";
await GeneratorSnapshotTest<EnumExtensionsGenerator>.RunAsync(
source,
Path.Combine(
TestContext.CurrentContext.TestDirectory,
"enums",
"snapshots",
"EnumExtensionsGenerator",
"EnumWithFlagValues"));
}
[Test]
public async Task Snapshot_DisableIsMethods()
{
const string source = """
using GFramework.SourceGenerators.Abstractions.enums;
namespace TestApp
{
[GenerateEnumExtensions(GenerateIsMethods = false)]
public enum Status
{
Active,
Inactive
}
}
""";
await GeneratorSnapshotTest<EnumExtensionsGenerator>.RunAsync(
source,
Path.Combine(
TestContext.CurrentContext.TestDirectory,
"enums",
"snapshots",
"EnumExtensionsGenerator",
"DisableIsMethods"));
}
[Test]
public async Task Snapshot_DisableIsInMethod()
{
const string source = """
using GFramework.SourceGenerators.Abstractions.enums;
namespace TestApp
{
[GenerateEnumExtensions(GenerateIsInMethod = false)]
public enum Status
{
Active,
Inactive
}
}
""";
await GeneratorSnapshotTest<EnumExtensionsGenerator>.RunAsync(
source,
Path.Combine(
TestContext.CurrentContext.TestDirectory,
"enums",
"snapshots",
"EnumExtensionsGenerator",
"DisableIsInMethod"));
}
}

View File

@ -0,0 +1,159 @@
using GFramework.SourceGenerators.logging;
using GFramework.SourceGenerators.Tests.core;
using NUnit.Framework;
namespace GFramework.SourceGenerators.Tests.logging;
[TestFixture]
public class LoggerGeneratorSnapshotTests
{
[Test]
public async Task Snapshot_DefaultConfiguration_Class()
{
const string source = """
using GFramework.SourceGenerators.Abstractions.logging;
namespace TestApp
{
[Log]
public partial class MyService
{
}
}
""";
await GeneratorSnapshotTest<LoggerGenerator>.RunAsync(
source,
Path.Combine(
TestContext.CurrentContext.TestDirectory,
"logging",
"snapshots",
"LoggerGenerator",
"DefaultConfiguration_Class"));
}
[Test]
public async Task Snapshot_CustomName_Class()
{
const string source = """
using GFramework.SourceGenerators.Abstractions.logging;
namespace TestApp
{
[Log(Name = "CustomLogger")]
public partial class MyService
{
}
}
""";
await GeneratorSnapshotTest<LoggerGenerator>.RunAsync(
source,
Path.Combine(
TestContext.CurrentContext.TestDirectory,
"logging",
"snapshots",
"LoggerGenerator",
"CustomName_Class"));
}
[Test]
public async Task Snapshot_CustomFieldName_Class()
{
const string source = """
using GFramework.SourceGenerators.Abstractions.logging;
namespace TestApp
{
[Log(FieldName = "MyLogger")]
public partial class MyService
{
}
}
""";
await GeneratorSnapshotTest<LoggerGenerator>.RunAsync(
source,
Path.Combine(
TestContext.CurrentContext.TestDirectory,
"logging",
"snapshots",
"LoggerGenerator",
"CustomFieldName_Class"));
}
[Test]
public async Task Snapshot_InstanceField_Class()
{
const string source = """
using GFramework.SourceGenerators.Abstractions.logging;
namespace TestApp
{
[Log(IsStatic = false)]
public partial class MyService
{
}
}
""";
await GeneratorSnapshotTest<LoggerGenerator>.RunAsync(
source,
Path.Combine(
TestContext.CurrentContext.TestDirectory,
"logging",
"snapshots",
"LoggerGenerator",
"InstanceField_Class"));
}
[Test]
public async Task Snapshot_PublicField_Class()
{
const string source = """
using GFramework.SourceGenerators.Abstractions.logging;
namespace TestApp
{
[Log(AccessModifier = "public")]
public partial class MyService
{
}
}
""";
await GeneratorSnapshotTest<LoggerGenerator>.RunAsync(
source,
Path.Combine(
TestContext.CurrentContext.TestDirectory,
"logging",
"snapshots",
"LoggerGenerator",
"PublicField_Class"));
}
[Test]
public async Task Snapshot_GenericClass()
{
const string source = """
using GFramework.SourceGenerators.Abstractions.logging;
namespace TestApp
{
[Log]
public partial class MyService<T>
{
}
}
""";
await GeneratorSnapshotTest<LoggerGenerator>.RunAsync(
source,
Path.Combine(
TestContext.CurrentContext.TestDirectory,
"logging",
"snapshots",
"LoggerGenerator",
"GenericClass"));
}
}