Sar*_*yan 6 .net c# unit-testing async-await cancellationtokensource
我最近编写了一个异步方法,调用外部长时间运行的异步方法,所以我决定通过CancellationToken启用取消.该方法可以同时调用.
实现结合了Stephen Cleary的书" Concurrency in C#Cookbook "中描述的指数退避和超时技术,如下所示;
/// <summary>
/// Sets bar
/// </summary>
/// <param name="cancellationToken">The cancellation token that cancels the operation</param>
/// <returns>A <see cref="Task"/> representing the task of setting bar value</returns>
/// <exception cref="OperationCanceledException">Is thrown when the task is cancelled via <paramref name="cancellationToken"/></exception>
/// <exception cref="TimeoutException">Is thrown when unable to get bar value due to time out</exception>
public async Task FooAsync(CancellationToken cancellationToken)
{
TimeSpan delay = TimeSpan.FromMilliseconds(250);
for (int i = 0; i < RetryLimit; i++)
{
if (i != 0)
{
await Task.Delay(delay, cancellationToken);
delay += delay; // Exponential backoff
}
await semaphoreSlim.WaitAsync(cancellationToken); // Critical section is introduced for long running operation to prevent race condition
using (CancellationTokenSource cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
{
cancellationTokenSource.CancelAfter(TimeSpan.FromMilliseconds(Timeout));
CancellationToken linkedCancellationToken = cancellationTokenSource.Token;
try
{
cancellationToken.ThrowIfCancellationRequested();
bar = await barService.GetBarAsync(barId, linkedCancellationToken).ConfigureAwait(false);
break;
}
catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
{
if (i == RetryLimit - 1)
{
throw new TimeoutException("Unable to get bar, operation timed out!");
}
// Otherwise, exception is ignored. Will give it another try
}
finally
{
semaphoreSlim.Release();
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
我想知道我是否应该编写一个单元测试,明确断言内部任务在barService.GetBarAsync()被取消时FooAsync()被取消.如果是这样,如何干净利落地实施?
最重要的是,我应该忽略实现细节,只测试方法摘要中描述的客户端/调用者(条形图已更新,取消触发器OperationCanceledException,超时触发器TimeoutException).
如果没有,我应该弄湿自己的脚并开始对以下情况进行单元测试:
我想知道我是否应该编写一个单元测试,明确断言每当 FooAsync() 取消时内部任务 barService.GetBarAsync() 就会取消。
编写一个测试断言传递给的取消标记会更容易,GetBarAsync只要传递给的取消标记FooAsync被取消。
对于异步单元测试,我选择的信号是TaskCompletionSource<object>异步信号和ManualResetEvent同步信号。由于GetBarAsync是异步的,我会使用异步的,例如,
var cts = new CancellationTokenSource(); // passed into FooAsync
var getBarAsyncReady = new TaskCompletionSource<object>();
var getBarAsyncContinue = new TaskCompletionSource<object>();
bool triggered = false;
[inject] GetBarAsync = async (barId, cancellationToken) =>
{
getBarAsyncReady.SetResult(null);
await getBarAsyncContinue.Task;
triggered = cancellationToken.IsCancellationRequested;
cancellationToken.ThrowIfCancellationRequested();
};
var task = FooAsync(cts.Token);
await getBarAsyncReady.Task;
cts.Cancel();
getBarAsyncContinue.SetResult(null);
Assert(triggered);
Assert(task throws OperationCanceledException);
Run Code Online (Sandbox Code Playgroud)
您可以使用这样的信号来创建一种“锁定步骤”。
旁注:在我自己的代码中,我从不编写重试逻辑。我使用Polly,它完全async兼容并经过全面测试。这会将需要测试的语义减少到:
OperationCanceledException触发时。TimeoutException.(1) 将像上面那样完成。(2) 和 (3) 不太容易测试(对于正确的测试,需要 MS Fakes 或时间/互斥体的抽象)。在单元测试方面肯定有一个收益递减点,这取决于您想要走多远。
| 归档时间: |
|
| 查看次数: |
3356 次 |
| 最近记录: |