mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-22 10:34:30 +08:00
- 移除旧的Arrange步骤,直接在Act步骤中调用AsyncExtensions.WithTimeout方法 - 添加新的测试用例验证超时时内部任务能够被正确取消 - 修改现有测试用例适配新的方法调用方式 - 简化异常处理相关的断言逻辑
319 lines
8.9 KiB
C#
319 lines
8.9 KiB
C#
using System.Diagnostics;
|
|
using GFramework.Core.extensions;
|
|
using NUnit.Framework;
|
|
|
|
namespace GFramework.Core.Tests.extensions;
|
|
|
|
/// <summary>
|
|
/// 测试 AsyncExtensions 扩展方法的功能
|
|
/// </summary>
|
|
[TestFixture]
|
|
public class AsyncExtensionsTests
|
|
{
|
|
[Test]
|
|
public async Task WithTimeout_Should_Return_Result_When_Task_Completes_Before_Timeout()
|
|
{
|
|
// Act
|
|
var result = await AsyncExtensions.WithTimeout(
|
|
_ => Task.FromResult(42),
|
|
TimeSpan.FromSeconds(1));
|
|
|
|
// Assert
|
|
Assert.That(result, Is.EqualTo(42));
|
|
}
|
|
|
|
[Test]
|
|
public void WithTimeout_Should_Throw_TimeoutException_When_Task_Exceeds_Timeout()
|
|
{
|
|
// Act & Assert
|
|
Assert.ThrowsAsync<TimeoutException>(async () =>
|
|
await AsyncExtensions.WithTimeout(
|
|
async ct =>
|
|
{
|
|
await Task.Delay(TimeSpan.FromSeconds(2), ct);
|
|
return 42;
|
|
},
|
|
TimeSpan.FromMilliseconds(100)));
|
|
}
|
|
|
|
[Test]
|
|
public void WithTimeout_Should_Throw_OperationCanceledException_When_Cancellation_Requested()
|
|
{
|
|
// Arrange
|
|
using var cts = new CancellationTokenSource();
|
|
cts.Cancel();
|
|
|
|
// Act & Assert
|
|
Assert.ThrowsAsync<OperationCanceledException>(async () =>
|
|
await AsyncExtensions.WithTimeout(
|
|
async ct =>
|
|
{
|
|
await Task.Delay(TimeSpan.FromSeconds(2), ct);
|
|
return 42;
|
|
},
|
|
TimeSpan.FromSeconds(1),
|
|
cts.Token));
|
|
}
|
|
|
|
[Test]
|
|
public void WithTimeout_Should_Cancel_Inner_Task_When_Timeout_Elapses()
|
|
{
|
|
// Arrange
|
|
var innerTaskCanceled = false;
|
|
|
|
// Act & Assert
|
|
Assert.ThrowsAsync<TimeoutException>(async () =>
|
|
await AsyncExtensions.WithTimeout(
|
|
async ct =>
|
|
{
|
|
try
|
|
{
|
|
await Task.Delay(TimeSpan.FromSeconds(5), ct);
|
|
return 0;
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
innerTaskCanceled = true;
|
|
throw;
|
|
}
|
|
},
|
|
TimeSpan.FromMilliseconds(100)));
|
|
|
|
Assert.That(innerTaskCanceled, Is.True, "内部任务应在超时时收到取消信号");
|
|
}
|
|
|
|
[Test]
|
|
public async Task WithTimeout_NoResult_Should_Complete_When_Task_Completes_Before_Timeout()
|
|
{
|
|
// Arrange
|
|
var stopwatch = Stopwatch.StartNew();
|
|
|
|
// Act
|
|
await AsyncExtensions.WithTimeout(
|
|
_ => Task.CompletedTask,
|
|
TimeSpan.FromSeconds(1));
|
|
stopwatch.Stop();
|
|
|
|
// Assert
|
|
Assert.That(stopwatch.ElapsedMilliseconds, Is.LessThan(1000), "Task should complete before timeout");
|
|
Assert.Pass("Task completed successfully within timeout period");
|
|
}
|
|
|
|
[Test]
|
|
public void WithTimeout_NoResult_Should_Throw_TimeoutException_When_Task_Exceeds_Timeout()
|
|
{
|
|
// Act & Assert
|
|
Assert.ThrowsAsync<TimeoutException>(async () =>
|
|
await AsyncExtensions.WithTimeout(
|
|
ct => Task.Delay(TimeSpan.FromSeconds(2), ct),
|
|
TimeSpan.FromMilliseconds(100)));
|
|
}
|
|
|
|
[Test]
|
|
public void WithTimeout_NoResult_Should_Cancel_Inner_Task_When_Timeout_Elapses()
|
|
{
|
|
// Arrange
|
|
var innerTaskCanceled = false;
|
|
|
|
// Act & Assert
|
|
Assert.ThrowsAsync<TimeoutException>(async () =>
|
|
await AsyncExtensions.WithTimeout(
|
|
async ct =>
|
|
{
|
|
try
|
|
{
|
|
await Task.Delay(TimeSpan.FromSeconds(5), ct);
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
innerTaskCanceled = true;
|
|
throw;
|
|
}
|
|
},
|
|
TimeSpan.FromMilliseconds(100)));
|
|
|
|
Assert.That(innerTaskCanceled, Is.True, "内部任务应在超时时收到取消信号");
|
|
}
|
|
|
|
[Test]
|
|
public async Task WithRetry_Should_Return_Result_When_Task_Succeeds()
|
|
{
|
|
// Arrange
|
|
var attemptCount = 0;
|
|
Func<Task<int>> taskFactory = () =>
|
|
{
|
|
attemptCount++;
|
|
return Task.FromResult(42);
|
|
};
|
|
|
|
// Act
|
|
var result = await taskFactory.WithRetry(3, TimeSpan.FromMilliseconds(10));
|
|
|
|
// Assert
|
|
Assert.That(result, Is.EqualTo(42));
|
|
Assert.That(attemptCount, Is.EqualTo(1));
|
|
}
|
|
|
|
[Test]
|
|
public async Task WithRetry_Should_Retry_On_Failure()
|
|
{
|
|
// Arrange
|
|
var attemptCount = 0;
|
|
Func<Task<int>> taskFactory = () =>
|
|
{
|
|
attemptCount++;
|
|
if (attemptCount < 3)
|
|
throw new InvalidOperationException("Temporary failure");
|
|
return Task.FromResult(42);
|
|
};
|
|
|
|
// Act
|
|
var result = await taskFactory.WithRetry(3, TimeSpan.FromMilliseconds(10));
|
|
|
|
// Assert
|
|
Assert.That(result, Is.EqualTo(42));
|
|
Assert.That(attemptCount, Is.EqualTo(3));
|
|
}
|
|
|
|
[Test]
|
|
public void WithRetry_Should_Throw_AggregateException_When_All_Retries_Fail()
|
|
{
|
|
// Arrange
|
|
var attemptCount = 0;
|
|
Func<Task<int>> taskFactory = () =>
|
|
{
|
|
attemptCount++;
|
|
throw new InvalidOperationException("Permanent failure");
|
|
};
|
|
|
|
// Act & Assert
|
|
Assert.ThrowsAsync<AggregateException>(async () =>
|
|
await taskFactory.WithRetry(2, TimeSpan.FromMilliseconds(10)));
|
|
}
|
|
|
|
[Test]
|
|
public async Task WithRetry_Should_Respect_ShouldRetry_Predicate()
|
|
{
|
|
// Arrange
|
|
var attemptCount = 0;
|
|
Func<Task<int>> taskFactory = () =>
|
|
{
|
|
attemptCount++;
|
|
throw new ArgumentException("Should not retry");
|
|
};
|
|
|
|
// Act & Assert
|
|
Assert.ThrowsAsync<AggregateException>(async () =>
|
|
await taskFactory.WithRetry(3, TimeSpan.FromMilliseconds(10),
|
|
ex => ex is not ArgumentException));
|
|
|
|
await Task.Delay(50); // 等待任务完成
|
|
Assert.That(attemptCount, Is.EqualTo(1)); // 不应该重试
|
|
}
|
|
|
|
[Test]
|
|
public async Task TryAsync_Should_Return_Success_When_Task_Succeeds()
|
|
{
|
|
// Arrange
|
|
Func<Task<int>> func = () => Task.FromResult(42);
|
|
|
|
// Act
|
|
var result = await func.TryAsync();
|
|
|
|
// Assert
|
|
Assert.That(result.IsSuccess, Is.True);
|
|
Assert.That(result.IfFail(0), Is.EqualTo(42));
|
|
}
|
|
|
|
[Test]
|
|
public async Task TryAsync_Should_Return_Failure_When_Task_Throws()
|
|
{
|
|
// Arrange
|
|
Func<Task<int>> func = () => throw new InvalidOperationException("Test error");
|
|
|
|
// Act
|
|
var result = await func.TryAsync();
|
|
|
|
// Assert
|
|
Assert.That(result.IsFaulted, Is.True);
|
|
}
|
|
|
|
[Test]
|
|
public async Task WhenAll_Should_Wait_For_All_Tasks()
|
|
{
|
|
// Arrange
|
|
var task1 = Task.Delay(10);
|
|
var task2 = Task.Delay(20);
|
|
var task3 = Task.Delay(30);
|
|
var tasks = new[] { task1, task2, task3 };
|
|
|
|
// Act
|
|
await tasks.WhenAll();
|
|
|
|
// Assert
|
|
Assert.That(task1.IsCompleted, Is.True);
|
|
Assert.That(task2.IsCompleted, Is.True);
|
|
Assert.That(task3.IsCompleted, Is.True);
|
|
}
|
|
|
|
[Test]
|
|
public async Task WhenAll_WithResults_Should_Return_All_Results()
|
|
{
|
|
// Arrange
|
|
var task1 = Task.FromResult(1);
|
|
var task2 = Task.FromResult(2);
|
|
var task3 = Task.FromResult(3);
|
|
var tasks = new[] { task1, task2, task3 };
|
|
|
|
// Act
|
|
var results = await tasks.WhenAll();
|
|
|
|
// Assert
|
|
Assert.That(results, Is.EqualTo(new[] { 1, 2, 3 }));
|
|
}
|
|
|
|
[Test]
|
|
public async Task WithFallback_Should_Return_Result_When_Task_Succeeds()
|
|
{
|
|
// Arrange
|
|
var task = Task.FromResult(42);
|
|
|
|
// Act
|
|
var result = await task.WithFallback(_ => -1);
|
|
|
|
// Assert
|
|
Assert.That(result, Is.EqualTo(42));
|
|
}
|
|
|
|
[Test]
|
|
public async Task WithFallback_Should_Return_Fallback_Value_When_Task_Fails()
|
|
{
|
|
// Arrange
|
|
var task = Task.FromException<int>(new InvalidOperationException("Test error"));
|
|
|
|
// Act
|
|
var result = await task.WithFallback(_ => -1);
|
|
|
|
// Assert
|
|
Assert.That(result, Is.EqualTo(-1));
|
|
}
|
|
|
|
[Test]
|
|
public async Task WithFallback_Should_Pass_Exception_To_Fallback()
|
|
{
|
|
// Arrange
|
|
var expectedException = new InvalidOperationException("Test error");
|
|
var task = Task.FromException<int>(expectedException);
|
|
Exception? capturedEx = null;
|
|
|
|
// Act
|
|
await task.WithFallback(ex =>
|
|
{
|
|
capturedEx = ex;
|
|
return -1;
|
|
});
|
|
|
|
// Assert
|
|
Assert.That(capturedEx, Is.SameAs(expectedException));
|
|
}
|
|
} |