gpt4 book ai didi

c# - 单元测试异步方法 : How to explicitly assert that the internal task was cancelled

转载 作者:太空狗 更新时间:2023-10-29 21:38:09 25 4
gpt4 key购买 nike

我最近正在编写一个调用外部长时间运行的 async 方法的 async 方法,因此我决定传递 CancellationToken 以启用取消。该方法可以并发调用。

实现结合了指数退避超时 技术,如Stephen Cleary 所述。的书 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();
}
}
}
}

我想知道我是否应该编写一个单元测试,明确断言只要取消 FooAsync() 就会取消内部任务 barService.GetBarAsync()。如果是这样,如何干净地实现它?

最重要的是,我是否应该忽略实现细节,只按照方法摘要中的描述测试客户端/调用者关心的内容(条已更新,取消触发 OperationCanceledException,超时触发 TimeoutException)。

如果没有,我是否应该开始尝试并开始针对以下情况实现单元测试:

  1. 测试它是线程安全的(监视器一次只能由一个线程获取)
  2. 测试重试机制
  3. 测试服务器没有被淹没
  4. 测试甚至可能将常规异常传播给调用者

最佳答案

I wonder if I should write a unit test that explicitly asserts that the internal task barService.GetBarAsync() is cancelled whenever FooAsync() is cancelled.

编写断言取消 token 传递给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);

您可以使用这样的信号来创建一种“锁步”。


旁注:在我自己的代码中,我从不编写重试逻辑。我用 Polly , 完全是 async -兼容并经过全面测试。这会将需要测试的语义减少到:

  1. CT 通过(间接)传递给服务方法,导致 OperationCanceledException触发时。
  2. 还有一个超时,导致TimeoutException .
  3. 执行是互斥的。

(1) 将像上面那样完成。 (2) 和 (3) 不太容易测试(为了进行适当的测试,需要 MS Fakes 或时间/互斥体的抽象)。在单元测试方面肯定存在 yield 递减点,这取决于您想要走多远。

关于c# - 单元测试异步方法 : How to explicitly assert that the internal task was cancelled,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40083801/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com