mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-25 21:34:28 +08:00
refactor(coroutine): 移除命令等待事件协程扩展并改进测试
- 从 MediatorCoroutineExtensions 中移除 SendCommandAndWaitEventCoroutine 相关代码 - 从 CommandCoroutineExtensionsTests 中移除对应的测试方法 - 更新测试用例验证逻辑,统一使用静态方法调用方式 - 添加新的 MediatorCoroutineExtensionsTests 测试类 - 修改测试方法名称以更准确反映测试行为 - 统一异常处理和参数验证的测试覆盖
This commit is contained in:
parent
855b3f9eac
commit
984829c368
@ -133,10 +133,10 @@ public class CommandCoroutineExtensionsTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 验证SendCommandCoroutineWithErrorHandler应该处理null错误回调
|
/// 验证SendCommandCoroutineWithErrorHandler在无错误处理程序时应该抛出异常
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Test]
|
[Test]
|
||||||
public async Task SendCommandCoroutineWithErrorHandler_Should_Handle_Null_Error_Handler()
|
public void SendCommandCoroutineWithErrorHandler_Should_Throw_Exception_Without_Handler()
|
||||||
{
|
{
|
||||||
var command = new TestCommand();
|
var command = new TestCommand();
|
||||||
var contextAware = new TestContextAware();
|
var contextAware = new TestContextAware();
|
||||||
@ -147,21 +147,19 @@ public class CommandCoroutineExtensionsTests
|
|||||||
.Setup(ctx => ctx.SendCommandAsync(It.IsAny<IAsyncCommand>()))
|
.Setup(ctx => ctx.SendCommandAsync(It.IsAny<IAsyncCommand>()))
|
||||||
.Returns(Task.FromException(expectedException));
|
.Returns(Task.FromException(expectedException));
|
||||||
|
|
||||||
// 使用null作为错误处理程序
|
|
||||||
var coroutine = contextAware.SendCommandCoroutineWithErrorHandler(command);
|
var coroutine = contextAware.SendCommandCoroutineWithErrorHandler(command);
|
||||||
|
|
||||||
// 迭代协程直到完成
|
// 迭代协程应该抛出异常
|
||||||
while (coroutine.MoveNext())
|
Assert.Throws<InvalidOperationException>(() =>
|
||||||
{
|
{
|
||||||
if (coroutine.Current is WaitForTask waitForTask)
|
while (coroutine.MoveNext())
|
||||||
{
|
{
|
||||||
// 等待任务完成
|
if (coroutine.Current is WaitForTask waitForTask)
|
||||||
await Task.Delay(10);
|
{
|
||||||
|
Task.Delay(10).Wait();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
// 应该不会抛出异常
|
|
||||||
Assert.Pass();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -194,7 +192,8 @@ public class CommandCoroutineExtensionsTests
|
|||||||
.Setup(ctx => ctx.SendCommandAsync(It.IsAny<IAsyncCommand>()))
|
.Setup(ctx => ctx.SendCommandAsync(It.IsAny<IAsyncCommand>()))
|
||||||
.Returns(Task.CompletedTask);
|
.Returns(Task.CompletedTask);
|
||||||
|
|
||||||
var coroutine = contextAware.SendCommandAndWaitEventCoroutine<TestCommand, TestEvent>(
|
var coroutine = CommandCoroutineExtensions.SendCommandAndWaitEventCoroutine<TestCommand, TestEvent>(
|
||||||
|
contextAware,
|
||||||
command,
|
command,
|
||||||
ev =>
|
ev =>
|
||||||
{
|
{
|
||||||
@ -250,7 +249,8 @@ public class CommandCoroutineExtensionsTests
|
|||||||
// 对于超时情况,我们期望抛出TimeoutException
|
// 对于超时情况,我们期望抛出TimeoutException
|
||||||
Assert.Throws<TimeoutException>(() =>
|
Assert.Throws<TimeoutException>(() =>
|
||||||
{
|
{
|
||||||
var coroutine = contextAware.SendCommandAndWaitEventCoroutine<TestCommand, TestEvent>(
|
var coroutine = CommandCoroutineExtensions.SendCommandAndWaitEventCoroutine<TestCommand, TestEvent>(
|
||||||
|
contextAware,
|
||||||
command,
|
command,
|
||||||
null,
|
null,
|
||||||
0.1f); // 0.1秒超时
|
0.1f); // 0.1秒超时
|
||||||
@ -292,7 +292,8 @@ public class CommandCoroutineExtensionsTests
|
|||||||
.Returns(Task.CompletedTask);
|
.Returns(Task.CompletedTask);
|
||||||
|
|
||||||
// 使用null作为事件回调
|
// 使用null作为事件回调
|
||||||
var coroutine = contextAware.SendCommandAndWaitEventCoroutine<TestCommand, TestEvent>(
|
var coroutine = CommandCoroutineExtensions.SendCommandAndWaitEventCoroutine<TestCommand, TestEvent>(
|
||||||
|
contextAware,
|
||||||
command); // null回调
|
command); // null回调
|
||||||
|
|
||||||
// 启动协程
|
// 启动协程
|
||||||
@ -334,7 +335,8 @@ public class CommandCoroutineExtensionsTests
|
|||||||
.Setup(ctx => ctx.SendCommandAsync(It.IsAny<IAsyncCommand>()))
|
.Setup(ctx => ctx.SendCommandAsync(It.IsAny<IAsyncCommand>()))
|
||||||
.Returns(Task.FromException(expectedException));
|
.Returns(Task.FromException(expectedException));
|
||||||
|
|
||||||
var coroutine = contextAware.SendCommandAndWaitEventCoroutine<TestCommand, TestEvent>(
|
var coroutine = CommandCoroutineExtensions.SendCommandAndWaitEventCoroutine<TestCommand, TestEvent>(
|
||||||
|
contextAware,
|
||||||
command,
|
command,
|
||||||
_ => { });
|
_ => { });
|
||||||
|
|
||||||
@ -391,7 +393,8 @@ public class CommandCoroutineExtensionsTests
|
|||||||
.Setup(ctx => ctx.SendCommandAsync(It.IsAny<IAsyncCommand>()))
|
.Setup(ctx => ctx.SendCommandAsync(It.IsAny<IAsyncCommand>()))
|
||||||
.Returns(Task.CompletedTask);
|
.Returns(Task.CompletedTask);
|
||||||
|
|
||||||
var coroutine = contextAware.SendCommandAndWaitEventCoroutine<TestCommand, TestEvent>(
|
var coroutine = CommandCoroutineExtensions.SendCommandAndWaitEventCoroutine<TestCommand, TestEvent>(
|
||||||
|
contextAware,
|
||||||
command,
|
command,
|
||||||
ev => { });
|
ev => { });
|
||||||
|
|
||||||
@ -399,10 +402,30 @@ public class CommandCoroutineExtensionsTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 验证SendCommandAndWaitEventCoroutine应该在事件总线为null时处理异常
|
/// 验证SendCommandAndWaitEventCoroutine应该在timeout小于0时抛出ArgumentOutOfRangeException
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Test]
|
[Test]
|
||||||
public void SendCommandAndWaitEventCoroutine_Should_Handle_Null_EventBus()
|
public void SendCommandAndWaitEventCoroutine_Should_Throw_ArgumentOutOfRange_When_Timeout_Negative()
|
||||||
|
{
|
||||||
|
var command = new TestCommand();
|
||||||
|
var contextAware = new TestContextAware();
|
||||||
|
|
||||||
|
// 在创建协程时就应该抛出异常
|
||||||
|
Assert.Throws<ArgumentOutOfRangeException>(() =>
|
||||||
|
{
|
||||||
|
CommandCoroutineExtensions.SendCommandAndWaitEventCoroutine<TestCommand, TestEvent>(
|
||||||
|
contextAware,
|
||||||
|
command,
|
||||||
|
null,
|
||||||
|
-1.0f);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 验证SendCommandAndWaitEventCoroutine应该在事件总线为null时抛出InvalidOperationException
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void SendCommandAndWaitEventCoroutine_Should_Throw_When_EventBus_Null()
|
||||||
{
|
{
|
||||||
var command = new TestCommand();
|
var command = new TestCommand();
|
||||||
var contextAware = new TestContextAware();
|
var contextAware = new TestContextAware();
|
||||||
@ -413,11 +436,12 @@ public class CommandCoroutineExtensionsTests
|
|||||||
.Returns((IEventBus?)null);
|
.Returns((IEventBus?)null);
|
||||||
|
|
||||||
// 创建协程
|
// 创建协程
|
||||||
var coroutine = contextAware.SendCommandAndWaitEventCoroutine<TestCommand, TestEvent>(
|
var coroutine = CommandCoroutineExtensions.SendCommandAndWaitEventCoroutine<TestCommand, TestEvent>(
|
||||||
|
contextAware,
|
||||||
command,
|
command,
|
||||||
ev => { });
|
ev => { });
|
||||||
|
|
||||||
// 调用 MoveNext 时应该抛出异常,因为事件总线为null
|
// 调用 MoveNext 时应该抛出 InvalidOperationException
|
||||||
Assert.Throws<ArgumentNullException>(() => coroutine.MoveNext());
|
Assert.Throws<InvalidOperationException>(() => coroutine.MoveNext());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -0,0 +1,105 @@
|
|||||||
|
// Copyright (c) 2026 GeWuYou
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
using GFramework.Core.Abstractions.architecture;
|
||||||
|
using GFramework.Core.Abstractions.coroutine;
|
||||||
|
using GFramework.Core.Abstractions.rule;
|
||||||
|
using GFramework.Core.coroutine.extensions;
|
||||||
|
using Mediator;
|
||||||
|
using Moq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace GFramework.Core.Tests.coroutine;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// MediatorCoroutineExtensions的单元测试类
|
||||||
|
/// 测试Mediator模式与协程集成的扩展方法
|
||||||
|
/// 注意:由于 Mediator 使用源生成器,本测试类主要验证接口和参数验证
|
||||||
|
/// </summary>
|
||||||
|
[TestFixture]
|
||||||
|
public class MediatorCoroutineExtensionsTests
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 测试用的简单命令类
|
||||||
|
/// </summary>
|
||||||
|
private class TestCommand : IRequest<Unit>
|
||||||
|
{
|
||||||
|
public string Data { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 测试用的简单事件类
|
||||||
|
/// </summary>
|
||||||
|
private class TestEvent
|
||||||
|
{
|
||||||
|
public string Data { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 上下文感知基类的模拟实现
|
||||||
|
/// </summary>
|
||||||
|
private class TestContextAware : IContextAware
|
||||||
|
{
|
||||||
|
public readonly Mock<IArchitectureContext> _mockContext = new();
|
||||||
|
|
||||||
|
public IArchitectureContext GetContext()
|
||||||
|
{
|
||||||
|
return _mockContext.Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetContext(IArchitectureContext context)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 验证SendCommandCoroutine应该返回IEnumerator<IYieldInstruction>
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void SendCommandCoroutine_Should_Return_IEnumerator_Of_YieldInstruction()
|
||||||
|
{
|
||||||
|
var command = new TestCommand { Data = "Test" };
|
||||||
|
var contextAware = new TestContextAware();
|
||||||
|
|
||||||
|
// 创建 mediator 模拟
|
||||||
|
var mediatorMock = new Mock<IMediator>();
|
||||||
|
contextAware._mockContext
|
||||||
|
.Setup(ctx => ctx.GetService<IMediator>())
|
||||||
|
.Returns(mediatorMock.Object);
|
||||||
|
|
||||||
|
var coroutine = MediatorCoroutineExtensions.SendCommandCoroutine(contextAware, command);
|
||||||
|
|
||||||
|
Assert.That(coroutine, Is.InstanceOf<IEnumerator<IYieldInstruction>>());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 验证SendCommandCoroutine应该在mediator为null时抛出NullReferenceException
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void SendCommandCoroutine_Should_Throw_When_Mediator_Null()
|
||||||
|
{
|
||||||
|
var command = new TestCommand { Data = "Test" };
|
||||||
|
var contextAware = new TestContextAware();
|
||||||
|
|
||||||
|
// 设置上下文服务以返回null mediator
|
||||||
|
contextAware._mockContext
|
||||||
|
.Setup(ctx => ctx.GetService<IMediator>())
|
||||||
|
.Returns((IMediator?)null);
|
||||||
|
|
||||||
|
// 创建协程
|
||||||
|
var coroutine = MediatorCoroutineExtensions.SendCommandCoroutine(contextAware, command);
|
||||||
|
|
||||||
|
// 调用 MoveNext 时应该抛出 NullReferenceException
|
||||||
|
Assert.Throws<NullReferenceException>(() => coroutine.MoveNext());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -12,9 +12,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
using GFramework.Core.Abstractions.coroutine;
|
using GFramework.Core.Abstractions.coroutine;
|
||||||
using GFramework.Core.Abstractions.events;
|
|
||||||
using GFramework.Core.Abstractions.rule;
|
using GFramework.Core.Abstractions.rule;
|
||||||
using GFramework.Core.coroutine.instructions;
|
|
||||||
using Mediator;
|
using Mediator;
|
||||||
|
|
||||||
namespace GFramework.Core.coroutine.extensions;
|
namespace GFramework.Core.coroutine.extensions;
|
||||||
@ -53,111 +51,4 @@ public static class MediatorCoroutineExtensions
|
|||||||
else
|
else
|
||||||
throw task.Exception!.InnerException ?? task.Exception;
|
throw task.Exception!.InnerException ?? task.Exception;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ... existing code ...
|
|
||||||
/// <summary>
|
|
||||||
/// 发送命令并等待特定事件的协程实现。
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="TCommand">命令类型</typeparam>
|
|
||||||
/// <typeparam name="TEvent">要等待的事件类型</typeparam>
|
|
||||||
/// <param name="contextAware">上下文对象</param>
|
|
||||||
/// <param name="command">要发送的命令</param>
|
|
||||||
/// <param name="onEvent">事件触发时的回调</param>
|
|
||||||
/// <param name="timeout">
|
|
||||||
/// 超时时间(秒):
|
|
||||||
/// <list type="bullet">
|
|
||||||
/// <item><description>timeout < 0: 无效,将抛出 ArgumentOutOfRangeException</description></item>
|
|
||||||
/// <item><description>timeout == 0: 无超时,永久等待</description></item>
|
|
||||||
/// <item><description>timeout > 0: 启用超时机制</description></item>
|
|
||||||
/// </list>
|
|
||||||
/// </param>
|
|
||||||
/// <exception cref="ArgumentOutOfRangeException">当 timeout 小于 0 时抛出。</exception>
|
|
||||||
public static IEnumerator<IYieldInstruction> SendCommandAndWaitEventCoroutine<TCommand, TEvent>(
|
|
||||||
this IContextAware contextAware,
|
|
||||||
TCommand command,
|
|
||||||
Action<TEvent>? onEvent = null,
|
|
||||||
float timeout = 0f)
|
|
||||||
where TCommand : notnull
|
|
||||||
where TEvent : class
|
|
||||||
{
|
|
||||||
// 参数验证
|
|
||||||
ValidateParameters(timeout);
|
|
||||||
|
|
||||||
// 获取必要的服务
|
|
||||||
var context = contextAware.GetContext();
|
|
||||||
var mediator = context.GetService<IMediator>()
|
|
||||||
?? throw new InvalidOperationException("IMediator not found.");
|
|
||||||
var eventBus = context.GetService<IEventBus>()
|
|
||||||
?? throw new InvalidOperationException("IEventBus not found.");
|
|
||||||
|
|
||||||
// 执行协程逻辑
|
|
||||||
return ExecuteSendCommandAndWaitEventCoroutine(mediator, eventBus, command, onEvent, timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 验证方法参数的有效性。
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="timeout">超时时间</param>
|
|
||||||
/// <exception cref="ArgumentOutOfRangeException">当 timeout 小于 0 时抛出。</exception>
|
|
||||||
private static void ValidateParameters(float timeout)
|
|
||||||
{
|
|
||||||
if (timeout < 0f)
|
|
||||||
throw new ArgumentOutOfRangeException(
|
|
||||||
nameof(timeout),
|
|
||||||
timeout,
|
|
||||||
"Timeout must be greater than or equal to 0.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 执行发送命令并等待事件的协程逻辑。
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="TCommand">命令类型</typeparam>
|
|
||||||
/// <typeparam name="TEvent">事件类型</typeparam>
|
|
||||||
/// <param name="mediator">中介者服务</param>
|
|
||||||
/// <param name="eventBus">事件总线服务</param>
|
|
||||||
/// <param name="command">要发送的命令</param>
|
|
||||||
/// <param name="onEvent">事件回调</param>
|
|
||||||
/// <param name="timeout">超时时间</param>
|
|
||||||
/// <returns>协程枚举器</returns>
|
|
||||||
private static IEnumerator<IYieldInstruction> ExecuteSendCommandAndWaitEventCoroutine<TCommand, TEvent>(
|
|
||||||
IMediator mediator,
|
|
||||||
IEventBus eventBus,
|
|
||||||
TCommand command,
|
|
||||||
Action<TEvent>? onEvent,
|
|
||||||
float timeout)
|
|
||||||
where TCommand : notnull
|
|
||||||
where TEvent : class
|
|
||||||
{
|
|
||||||
WaitForEvent<TEvent>? wait = null;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
wait = new WaitForEvent<TEvent>(eventBus);
|
|
||||||
|
|
||||||
var task = mediator.Send(command).AsTask();
|
|
||||||
yield return task.AsCoroutineInstruction();
|
|
||||||
|
|
||||||
if (timeout > 0f)
|
|
||||||
{
|
|
||||||
var timeoutWait = new WaitForEventWithTimeout<TEvent>(wait, timeout);
|
|
||||||
yield return timeoutWait;
|
|
||||||
|
|
||||||
if (timeoutWait.IsTimeout)
|
|
||||||
throw new TimeoutException(
|
|
||||||
$"Wait for event {typeof(TEvent).Name} timeout.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
yield return wait;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wait.EventData != null)
|
|
||||||
onEvent?.Invoke(wait.EventData);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
wait?.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// ... existing code ...
|
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user