From 82e6332a9b7a8bc7b62bea2728ccc44ad9bd3d26 Mon Sep 17 00:00:00 2001
From: GeWuYou <95328647+GeWuYou@users.noreply.github.com>
Date: Wed, 15 Apr 2026 17:47:54 +0800
Subject: [PATCH] =?UTF-8?q?test(core):=20=E6=B7=BB=E5=8A=A0=E6=9E=B6?=
=?UTF-8?q?=E6=9E=84=E4=B8=8A=E4=B8=8B=E6=96=87=E5=92=8C=E4=BE=9D=E8=B5=96?=
=?UTF-8?q?=E6=B3=A8=E5=85=A5=E5=AE=B9=E5=99=A8=E7=9A=84=E5=8D=95=E5=85=83?=
=?UTF-8?q?=E6=B5=8B=E8=AF=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 实现 ArchitectureContext 类的全面单元测试,覆盖构造函数、查询命令事件发送等功能
- 添加 MicrosoftDiContainer 依赖注入容器的完整测试,包括注册、获取、冻结等操作
- 创建 CqrsTestRuntime 测试基础设施,提供对 CQRS 处理器注册的受控访问
- 测试并发场景下的线程安全性,验证多线程环境下容器操作的正确性
- 实现优先级排序功能测试,确保服务按优先级正确排序和注册
- 添加各种边界条件测试,包括空参数异常处理和重复注册异常检测
---
.../Architectures/ArchitectureContextTests.cs | 29 +++++++++-------
.../Ioc/MicrosoftDiContainerTests.cs | 21 ++++++++++--
GFramework.Tests.Common/CqrsTestRuntime.cs | 34 ++++++++++++++-----
3 files changed, 60 insertions(+), 24 deletions(-)
diff --git a/GFramework.Core.Tests/Architectures/ArchitectureContextTests.cs b/GFramework.Core.Tests/Architectures/ArchitectureContextTests.cs
index 4b5da422..63d7ffae 100644
--- a/GFramework.Core.Tests/Architectures/ArchitectureContextTests.cs
+++ b/GFramework.Core.Tests/Architectures/ArchitectureContextTests.cs
@@ -16,7 +16,6 @@ using GFramework.Core.Events;
using GFramework.Core.Ioc;
using GFramework.Core.Logging;
using GFramework.Core.Query;
-using GFramework.Cqrs.Abstractions.Cqrs;
namespace GFramework.Core.Tests.Architectures;
@@ -45,6 +44,15 @@ namespace GFramework.Core.Tests.Architectures;
[TestFixture]
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]
public void SetUp()
{
@@ -76,14 +84,6 @@ public class ArchitectureContextTests
_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;
-
///
/// 测试构造函数在所有参数都有效时不应抛出异常
///
@@ -308,8 +308,10 @@ public class ArchitectureContextTests
[Test]
public async Task SendRequestAsync_Should_ResolveCqrsRuntime_OnlyOnce_When_AccessedConcurrently()
{
+ const int workerCount = 16;
using var startGate = new ManualResetEventSlim(false);
using var allowResolutionToComplete = new ManualResetEventSlim(false);
+ using var workersReady = new CountdownEvent(workerCount);
var resolutionCallCount = 0;
var runtime = new Mock(MockBehavior.Strict);
var container = new Mock(MockBehavior.Strict);
@@ -329,14 +331,19 @@ public class ArchitectureContextTests
});
var context = new ArchitectureContext(container.Object);
- var requests = Enumerable.Range(0, 16)
+ var requests = Enumerable.Range(0, workerCount)
.Select(_ => Task.Run(async () =>
{
+ workersReady.Signal();
startGate.Wait();
return await context.SendRequestAsync(new TestCqrsRequest());
}))
.ToArray();
+ Assert.That(
+ workersReady.Wait(TimeSpan.FromSeconds(1)),
+ Is.True,
+ "Expected all workers to be ready before releasing start gate.");
startGate.Set();
Assert.That(
@@ -344,8 +351,6 @@ public class ArchitectureContextTests
Is.True,
"Expected at least one CQRS runtime resolution attempt.");
- // 留出一个短暂窗口,让并发首次访问都在 runtime 尚未发布前抵达同一初始化点。
- await Task.Delay(50);
allowResolutionToComplete.Set();
var responses = await Task.WhenAll(requests);
diff --git a/GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs b/GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs
index 3faedecb..275101d9 100644
--- a/GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs
+++ b/GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs
@@ -1,10 +1,10 @@
using System.Reflection;
using GFramework.Core.Abstractions.Bases;
+using GFramework.Core.Abstractions.Cqrs;
using GFramework.Core.Ioc;
using GFramework.Core.Logging;
using GFramework.Core.Tests.Cqrs;
using GFramework.Core.Tests.Systems;
-using GFramework.Cqrs.Abstractions.Cqrs;
namespace GFramework.Core.Tests.Ioc;
@@ -14,6 +14,8 @@ namespace GFramework.Core.Tests.Ioc;
[TestFixture]
public class MicrosoftDiContainerTests
{
+ private MicrosoftDiContainer _container = null!;
+
///
/// 在每个测试方法执行前进行设置
///
@@ -33,8 +35,6 @@ public class MicrosoftDiContainerTests
CqrsTestRuntime.RegisterInfrastructure(_container);
}
- private MicrosoftDiContainer _container = null!;
-
///
/// 测试注册单例实例的功能
///
@@ -151,6 +151,21 @@ public class MicrosoftDiContainerTests
Assert.That(result, Is.SameAs(instance));
}
+ ///
+ /// 测试当 CQRS 基础设施已手动接线后,再调用处理器注册入口不会重复注册 runtime seam。
+ ///
+ [Test]
+ public void RegisterHandlers_Should_Not_Duplicate_Cqrs_Infrastructure_When_It_Is_Already_Registered()
+ {
+ Assert.That(_container.GetAll(), Has.Count.EqualTo(1));
+ Assert.That(_container.GetAll(), Has.Count.EqualTo(1));
+
+ CqrsTestRuntime.RegisterHandlers(_container);
+
+ Assert.That(_container.GetAll(), Has.Count.EqualTo(1));
+ Assert.That(_container.GetAll(), Has.Count.EqualTo(1));
+ }
+
///
/// 测试当没有实例时获取应返回 null 的功能
///
diff --git a/GFramework.Tests.Common/CqrsTestRuntime.cs b/GFramework.Tests.Common/CqrsTestRuntime.cs
index 50890658..57d4effd 100644
--- a/GFramework.Tests.Common/CqrsTestRuntime.cs
+++ b/GFramework.Tests.Common/CqrsTestRuntime.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Linq;
using System.Reflection;
using GFramework.Core.Abstractions.Cqrs;
using GFramework.Core.Abstractions.Ioc;
@@ -80,22 +79,31 @@ public static class CqrsTestRuntime
/// 为裸测试容器补齐默认 CQRS runtime seam。
///
/// 目标测试容器。
+ /// 为 。
+ /// 反射调用底层 CQRS runtime 或注册器构造函数失败时抛出。
///
/// 这使仅使用 的测试环境也能观察与生产路径一致的 runtime 行为,
/// 而无需完整启动服务模块管理器。
+ /// 该方法按服务类型执行幂等注册,只会补齐当前容器中尚未接线的 CQRS 基础设施。
///
public static void RegisterInfrastructure(MicrosoftDiContainer container)
{
ArgumentNullException.ThrowIfNull(container);
- var runtimeLogger = LoggerFactoryResolver.Provider.CreateLogger("CqrsDispatcher");
- var registrarLogger = LoggerFactoryResolver.Provider.CreateLogger(nameof(CqrsTestRuntime));
- var runtime = (ICqrsRuntime)CqrsDispatcherConstructor.Invoke([container, runtimeLogger]);
- var registrar =
- (ICqrsHandlerRegistrar)DefaultCqrsHandlerRegistrarConstructor.Invoke([container, registrarLogger]);
+ if (container.Get() is null)
+ {
+ var runtimeLogger = LoggerFactoryResolver.Provider.CreateLogger(CqrsDispatcherType.Name);
+ var runtime = (ICqrsRuntime)CqrsDispatcherConstructor.Invoke([container, runtimeLogger]);
+ container.Register(runtime);
+ }
- container.Register(runtime);
- container.Register(registrar);
+ if (container.Get() is null)
+ {
+ var registrarLogger = LoggerFactoryResolver.Provider.CreateLogger(DefaultCqrsHandlerRegistrarType.Name);
+ var registrar =
+ (ICqrsHandlerRegistrar)DefaultCqrsHandlerRegistrarConstructor.Invoke([container, registrarLogger]);
+ container.Register(registrar);
+ }
}
///
@@ -103,6 +111,14 @@ public static class CqrsTestRuntime
///
/// 承载处理器映射的测试容器。
/// 要扫描的程序集集合。
+ ///
+ /// 或 为 。
+ ///
+ /// 反射调用底层 CQRS 处理器注册入口失败时抛出。
+ ///
+ /// 该入口会自动调用 ,因此测试通常无需预先手动接线 CQRS 基础设施。
+ /// 程序集去重与空元素过滤由生产注册入口统一处理,避免测试辅助层复制相同筛选逻辑。
+ ///
public static void RegisterHandlers(MicrosoftDiContainer container, params Assembly[] assemblies)
{
ArgumentNullException.ThrowIfNull(container);
@@ -113,6 +129,6 @@ public static class CqrsTestRuntime
var logger = LoggerFactoryResolver.Provider.CreateLogger(nameof(CqrsTestRuntime));
RegisterHandlersMethod.Invoke(
null,
- [container, assemblies.Where(static assembly => assembly is not null).Distinct().ToArray(), logger]);
+ [container, assemblies, logger]);
}
}