mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-22 10:34:30 +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>
|
||||
/// 验证SendCommandCoroutineWithErrorHandler应该处理null错误回调
|
||||
/// 验证SendCommandCoroutineWithErrorHandler在无错误处理程序时应该抛出异常
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task SendCommandCoroutineWithErrorHandler_Should_Handle_Null_Error_Handler()
|
||||
public void SendCommandCoroutineWithErrorHandler_Should_Throw_Exception_Without_Handler()
|
||||
{
|
||||
var command = new TestCommand();
|
||||
var contextAware = new TestContextAware();
|
||||
@ -147,21 +147,19 @@ public class CommandCoroutineExtensionsTests
|
||||
.Setup(ctx => ctx.SendCommandAsync(It.IsAny<IAsyncCommand>()))
|
||||
.Returns(Task.FromException(expectedException));
|
||||
|
||||
// 使用null作为错误处理程序
|
||||
var coroutine = contextAware.SendCommandCoroutineWithErrorHandler(command);
|
||||
|
||||
// 迭代协程直到完成
|
||||
while (coroutine.MoveNext())
|
||||
// 迭代协程应该抛出异常
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
{
|
||||
if (coroutine.Current is WaitForTask waitForTask)
|
||||
while (coroutine.MoveNext())
|
||||
{
|
||||
// 等待任务完成
|
||||
await Task.Delay(10);
|
||||
if (coroutine.Current is WaitForTask waitForTask)
|
||||
{
|
||||
Task.Delay(10).Wait();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 应该不会抛出异常
|
||||
Assert.Pass();
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -194,7 +192,8 @@ public class CommandCoroutineExtensionsTests
|
||||
.Setup(ctx => ctx.SendCommandAsync(It.IsAny<IAsyncCommand>()))
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
var coroutine = contextAware.SendCommandAndWaitEventCoroutine<TestCommand, TestEvent>(
|
||||
var coroutine = CommandCoroutineExtensions.SendCommandAndWaitEventCoroutine<TestCommand, TestEvent>(
|
||||
contextAware,
|
||||
command,
|
||||
ev =>
|
||||
{
|
||||
@ -250,7 +249,8 @@ public class CommandCoroutineExtensionsTests
|
||||
// 对于超时情况,我们期望抛出TimeoutException
|
||||
Assert.Throws<TimeoutException>(() =>
|
||||
{
|
||||
var coroutine = contextAware.SendCommandAndWaitEventCoroutine<TestCommand, TestEvent>(
|
||||
var coroutine = CommandCoroutineExtensions.SendCommandAndWaitEventCoroutine<TestCommand, TestEvent>(
|
||||
contextAware,
|
||||
command,
|
||||
null,
|
||||
0.1f); // 0.1秒超时
|
||||
@ -292,7 +292,8 @@ public class CommandCoroutineExtensionsTests
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
// 使用null作为事件回调
|
||||
var coroutine = contextAware.SendCommandAndWaitEventCoroutine<TestCommand, TestEvent>(
|
||||
var coroutine = CommandCoroutineExtensions.SendCommandAndWaitEventCoroutine<TestCommand, TestEvent>(
|
||||
contextAware,
|
||||
command); // null回调
|
||||
|
||||
// 启动协程
|
||||
@ -334,7 +335,8 @@ public class CommandCoroutineExtensionsTests
|
||||
.Setup(ctx => ctx.SendCommandAsync(It.IsAny<IAsyncCommand>()))
|
||||
.Returns(Task.FromException(expectedException));
|
||||
|
||||
var coroutine = contextAware.SendCommandAndWaitEventCoroutine<TestCommand, TestEvent>(
|
||||
var coroutine = CommandCoroutineExtensions.SendCommandAndWaitEventCoroutine<TestCommand, TestEvent>(
|
||||
contextAware,
|
||||
command,
|
||||
_ => { });
|
||||
|
||||
@ -391,7 +393,8 @@ public class CommandCoroutineExtensionsTests
|
||||
.Setup(ctx => ctx.SendCommandAsync(It.IsAny<IAsyncCommand>()))
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
var coroutine = contextAware.SendCommandAndWaitEventCoroutine<TestCommand, TestEvent>(
|
||||
var coroutine = CommandCoroutineExtensions.SendCommandAndWaitEventCoroutine<TestCommand, TestEvent>(
|
||||
contextAware,
|
||||
command,
|
||||
ev => { });
|
||||
|
||||
@ -399,10 +402,30 @@ public class CommandCoroutineExtensionsTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证SendCommandAndWaitEventCoroutine应该在事件总线为null时处理异常
|
||||
/// 验证SendCommandAndWaitEventCoroutine应该在timeout小于0时抛出ArgumentOutOfRangeException
|
||||
/// </summary>
|
||||
[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 contextAware = new TestContextAware();
|
||||
@ -413,11 +436,12 @@ public class CommandCoroutineExtensionsTests
|
||||
.Returns((IEventBus?)null);
|
||||
|
||||
// 创建协程
|
||||
var coroutine = contextAware.SendCommandAndWaitEventCoroutine<TestCommand, TestEvent>(
|
||||
var coroutine = CommandCoroutineExtensions.SendCommandAndWaitEventCoroutine<TestCommand, TestEvent>(
|
||||
contextAware,
|
||||
command,
|
||||
ev => { });
|
||||
|
||||
// 调用 MoveNext 时应该抛出异常,因为事件总线为null
|
||||
Assert.Throws<ArgumentNullException>(() => coroutine.MoveNext());
|
||||
// 调用 MoveNext 时应该抛出 InvalidOperationException
|
||||
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.
|
||||
|
||||
using GFramework.Core.Abstractions.coroutine;
|
||||
using GFramework.Core.Abstractions.events;
|
||||
using GFramework.Core.Abstractions.rule;
|
||||
using GFramework.Core.coroutine.instructions;
|
||||
using Mediator;
|
||||
|
||||
namespace GFramework.Core.coroutine.extensions;
|
||||
@ -53,111 +51,4 @@ public static class MediatorCoroutineExtensions
|
||||
else
|
||||
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