mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-13 14:14:29 +08:00
test(core): 添加架构上下文和依赖注入容器的单元测试
- 实现 ArchitectureContext 类的全面单元测试,覆盖构造函数、查询命令事件发送等功能 - 添加 MicrosoftDiContainer 依赖注入容器的完整测试,包括注册、获取、冻结等操作 - 创建 CqrsTestRuntime 测试基础设施,提供对 CQRS 处理器注册的受控访问 - 测试并发场景下的线程安全性,验证多线程环境下容器操作的正确性 - 实现优先级排序功能测试,确保服务按优先级正确排序和注册 - 添加各种边界条件测试,包括空参数异常处理和重复注册异常检测
This commit is contained in:
parent
81897ce2ac
commit
82e6332a9b
@ -16,7 +16,6 @@ using GFramework.Core.Events;
|
|||||||
using GFramework.Core.Ioc;
|
using GFramework.Core.Ioc;
|
||||||
using GFramework.Core.Logging;
|
using GFramework.Core.Logging;
|
||||||
using GFramework.Core.Query;
|
using GFramework.Core.Query;
|
||||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
|
||||||
|
|
||||||
namespace GFramework.Core.Tests.Architectures;
|
namespace GFramework.Core.Tests.Architectures;
|
||||||
|
|
||||||
@ -45,6 +44,15 @@ namespace GFramework.Core.Tests.Architectures;
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class ArchitectureContextTests
|
public class ArchitectureContextTests
|
||||||
{
|
{
|
||||||
|
private AsyncQueryExecutor? _asyncQueryBus;
|
||||||
|
private CommandExecutor? _commandBus;
|
||||||
|
private MicrosoftDiContainer? _container;
|
||||||
|
|
||||||
|
private ArchitectureContext? _context;
|
||||||
|
private DefaultEnvironment? _environment;
|
||||||
|
private EventBus? _eventBus;
|
||||||
|
private QueryExecutor? _queryBus;
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void SetUp()
|
public void SetUp()
|
||||||
{
|
{
|
||||||
@ -76,14 +84,6 @@ public class ArchitectureContextTests
|
|||||||
_context = new ArchitectureContext(_container);
|
_context = new ArchitectureContext(_container);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ArchitectureContext? _context;
|
|
||||||
private MicrosoftDiContainer? _container;
|
|
||||||
private EventBus? _eventBus;
|
|
||||||
private CommandExecutor? _commandBus;
|
|
||||||
private QueryExecutor? _queryBus;
|
|
||||||
private AsyncQueryExecutor? _asyncQueryBus;
|
|
||||||
private DefaultEnvironment? _environment;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 测试构造函数在所有参数都有效时不应抛出异常
|
/// 测试构造函数在所有参数都有效时不应抛出异常
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -308,8 +308,10 @@ public class ArchitectureContextTests
|
|||||||
[Test]
|
[Test]
|
||||||
public async Task SendRequestAsync_Should_ResolveCqrsRuntime_OnlyOnce_When_AccessedConcurrently()
|
public async Task SendRequestAsync_Should_ResolveCqrsRuntime_OnlyOnce_When_AccessedConcurrently()
|
||||||
{
|
{
|
||||||
|
const int workerCount = 16;
|
||||||
using var startGate = new ManualResetEventSlim(false);
|
using var startGate = new ManualResetEventSlim(false);
|
||||||
using var allowResolutionToComplete = new ManualResetEventSlim(false);
|
using var allowResolutionToComplete = new ManualResetEventSlim(false);
|
||||||
|
using var workersReady = new CountdownEvent(workerCount);
|
||||||
var resolutionCallCount = 0;
|
var resolutionCallCount = 0;
|
||||||
var runtime = new Mock<ICqrsRuntime>(MockBehavior.Strict);
|
var runtime = new Mock<ICqrsRuntime>(MockBehavior.Strict);
|
||||||
var container = new Mock<IIocContainer>(MockBehavior.Strict);
|
var container = new Mock<IIocContainer>(MockBehavior.Strict);
|
||||||
@ -329,14 +331,19 @@ public class ArchitectureContextTests
|
|||||||
});
|
});
|
||||||
|
|
||||||
var context = new ArchitectureContext(container.Object);
|
var context = new ArchitectureContext(container.Object);
|
||||||
var requests = Enumerable.Range(0, 16)
|
var requests = Enumerable.Range(0, workerCount)
|
||||||
.Select(_ => Task.Run(async () =>
|
.Select(_ => Task.Run(async () =>
|
||||||
{
|
{
|
||||||
|
workersReady.Signal();
|
||||||
startGate.Wait();
|
startGate.Wait();
|
||||||
return await context.SendRequestAsync(new TestCqrsRequest());
|
return await context.SendRequestAsync(new TestCqrsRequest());
|
||||||
}))
|
}))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
|
Assert.That(
|
||||||
|
workersReady.Wait(TimeSpan.FromSeconds(1)),
|
||||||
|
Is.True,
|
||||||
|
"Expected all workers to be ready before releasing start gate.");
|
||||||
startGate.Set();
|
startGate.Set();
|
||||||
|
|
||||||
Assert.That(
|
Assert.That(
|
||||||
@ -344,8 +351,6 @@ public class ArchitectureContextTests
|
|||||||
Is.True,
|
Is.True,
|
||||||
"Expected at least one CQRS runtime resolution attempt.");
|
"Expected at least one CQRS runtime resolution attempt.");
|
||||||
|
|
||||||
// 留出一个短暂窗口,让并发首次访问都在 runtime 尚未发布前抵达同一初始化点。
|
|
||||||
await Task.Delay(50);
|
|
||||||
allowResolutionToComplete.Set();
|
allowResolutionToComplete.Set();
|
||||||
|
|
||||||
var responses = await Task.WhenAll(requests);
|
var responses = await Task.WhenAll(requests);
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using GFramework.Core.Abstractions.Bases;
|
using GFramework.Core.Abstractions.Bases;
|
||||||
|
using GFramework.Core.Abstractions.Cqrs;
|
||||||
using GFramework.Core.Ioc;
|
using GFramework.Core.Ioc;
|
||||||
using GFramework.Core.Logging;
|
using GFramework.Core.Logging;
|
||||||
using GFramework.Core.Tests.Cqrs;
|
using GFramework.Core.Tests.Cqrs;
|
||||||
using GFramework.Core.Tests.Systems;
|
using GFramework.Core.Tests.Systems;
|
||||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
|
||||||
|
|
||||||
namespace GFramework.Core.Tests.Ioc;
|
namespace GFramework.Core.Tests.Ioc;
|
||||||
|
|
||||||
@ -14,6 +14,8 @@ namespace GFramework.Core.Tests.Ioc;
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class MicrosoftDiContainerTests
|
public class MicrosoftDiContainerTests
|
||||||
{
|
{
|
||||||
|
private MicrosoftDiContainer _container = null!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 在每个测试方法执行前进行设置
|
/// 在每个测试方法执行前进行设置
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -33,8 +35,6 @@ public class MicrosoftDiContainerTests
|
|||||||
CqrsTestRuntime.RegisterInfrastructure(_container);
|
CqrsTestRuntime.RegisterInfrastructure(_container);
|
||||||
}
|
}
|
||||||
|
|
||||||
private MicrosoftDiContainer _container = null!;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 测试注册单例实例的功能
|
/// 测试注册单例实例的功能
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -151,6 +151,21 @@ public class MicrosoftDiContainerTests
|
|||||||
Assert.That(result, Is.SameAs(instance));
|
Assert.That(result, Is.SameAs(instance));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 测试当 CQRS 基础设施已手动接线后,再调用处理器注册入口不会重复注册 runtime seam。
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void RegisterHandlers_Should_Not_Duplicate_Cqrs_Infrastructure_When_It_Is_Already_Registered()
|
||||||
|
{
|
||||||
|
Assert.That(_container.GetAll<ICqrsRuntime>(), Has.Count.EqualTo(1));
|
||||||
|
Assert.That(_container.GetAll<ICqrsHandlerRegistrar>(), Has.Count.EqualTo(1));
|
||||||
|
|
||||||
|
CqrsTestRuntime.RegisterHandlers(_container);
|
||||||
|
|
||||||
|
Assert.That(_container.GetAll<ICqrsRuntime>(), Has.Count.EqualTo(1));
|
||||||
|
Assert.That(_container.GetAll<ICqrsHandlerRegistrar>(), Has.Count.EqualTo(1));
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 测试当没有实例时获取应返回 null 的功能
|
/// 测试当没有实例时获取应返回 null 的功能
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using GFramework.Core.Abstractions.Cqrs;
|
using GFramework.Core.Abstractions.Cqrs;
|
||||||
using GFramework.Core.Abstractions.Ioc;
|
using GFramework.Core.Abstractions.Ioc;
|
||||||
@ -80,29 +79,46 @@ public static class CqrsTestRuntime
|
|||||||
/// 为裸测试容器补齐默认 CQRS runtime seam。
|
/// 为裸测试容器补齐默认 CQRS runtime seam。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="container">目标测试容器。</param>
|
/// <param name="container">目标测试容器。</param>
|
||||||
|
/// <exception cref="ArgumentNullException"><paramref name="container" /> 为 <see langword="null" />。</exception>
|
||||||
|
/// <exception cref="TargetInvocationException">反射调用底层 CQRS runtime 或注册器构造函数失败时抛出。</exception>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// 这使仅使用 <see cref="MicrosoftDiContainer" /> 的测试环境也能观察与生产路径一致的 runtime 行为,
|
/// 这使仅使用 <see cref="MicrosoftDiContainer" /> 的测试环境也能观察与生产路径一致的 runtime 行为,
|
||||||
/// 而无需完整启动服务模块管理器。
|
/// 而无需完整启动服务模块管理器。
|
||||||
|
/// 该方法按服务类型执行幂等注册,只会补齐当前容器中尚未接线的 CQRS 基础设施。
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public static void RegisterInfrastructure(MicrosoftDiContainer container)
|
public static void RegisterInfrastructure(MicrosoftDiContainer container)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(container);
|
ArgumentNullException.ThrowIfNull(container);
|
||||||
|
|
||||||
var runtimeLogger = LoggerFactoryResolver.Provider.CreateLogger("CqrsDispatcher");
|
if (container.Get<ICqrsRuntime>() is null)
|
||||||
var registrarLogger = LoggerFactoryResolver.Provider.CreateLogger(nameof(CqrsTestRuntime));
|
{
|
||||||
|
var runtimeLogger = LoggerFactoryResolver.Provider.CreateLogger(CqrsDispatcherType.Name);
|
||||||
var runtime = (ICqrsRuntime)CqrsDispatcherConstructor.Invoke([container, runtimeLogger]);
|
var runtime = (ICqrsRuntime)CqrsDispatcherConstructor.Invoke([container, runtimeLogger]);
|
||||||
|
container.Register<ICqrsRuntime>(runtime);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (container.Get<ICqrsHandlerRegistrar>() is null)
|
||||||
|
{
|
||||||
|
var registrarLogger = LoggerFactoryResolver.Provider.CreateLogger(DefaultCqrsHandlerRegistrarType.Name);
|
||||||
var registrar =
|
var registrar =
|
||||||
(ICqrsHandlerRegistrar)DefaultCqrsHandlerRegistrarConstructor.Invoke([container, registrarLogger]);
|
(ICqrsHandlerRegistrar)DefaultCqrsHandlerRegistrarConstructor.Invoke([container, registrarLogger]);
|
||||||
|
|
||||||
container.Register<ICqrsRuntime>(runtime);
|
|
||||||
container.Register<ICqrsHandlerRegistrar>(registrar);
|
container.Register<ICqrsHandlerRegistrar>(registrar);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 通过与生产代码一致的注册入口扫描并注册指定程序集中的 CQRS 处理器。
|
/// 通过与生产代码一致的注册入口扫描并注册指定程序集中的 CQRS 处理器。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="container">承载处理器映射的测试容器。</param>
|
/// <param name="container">承载处理器映射的测试容器。</param>
|
||||||
/// <param name="assemblies">要扫描的程序集集合。</param>
|
/// <param name="assemblies">要扫描的程序集集合。</param>
|
||||||
|
/// <exception cref="ArgumentNullException">
|
||||||
|
/// <paramref name="container" /> 或 <paramref name="assemblies" /> 为 <see langword="null" />。
|
||||||
|
/// </exception>
|
||||||
|
/// <exception cref="TargetInvocationException">反射调用底层 CQRS 处理器注册入口失败时抛出。</exception>
|
||||||
|
/// <remarks>
|
||||||
|
/// 该入口会自动调用 <see cref="RegisterInfrastructure" />,因此测试通常无需预先手动接线 CQRS 基础设施。
|
||||||
|
/// 程序集去重与空元素过滤由生产注册入口统一处理,避免测试辅助层复制相同筛选逻辑。
|
||||||
|
/// </remarks>
|
||||||
public static void RegisterHandlers(MicrosoftDiContainer container, params Assembly[] assemblies)
|
public static void RegisterHandlers(MicrosoftDiContainer container, params Assembly[] assemblies)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(container);
|
ArgumentNullException.ThrowIfNull(container);
|
||||||
@ -113,6 +129,6 @@ public static class CqrsTestRuntime
|
|||||||
var logger = LoggerFactoryResolver.Provider.CreateLogger(nameof(CqrsTestRuntime));
|
var logger = LoggerFactoryResolver.Provider.CreateLogger(nameof(CqrsTestRuntime));
|
||||||
RegisterHandlersMethod.Invoke(
|
RegisterHandlersMethod.Invoke(
|
||||||
null,
|
null,
|
||||||
[container, assemblies.Where(static assembly => assembly is not null).Distinct().ToArray(), logger]);
|
[container, assemblies, logger]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user