模拟异步方法

6
我们正在使用MSTest和Moq编写异步代码的单元测试。我们有一些类似以下示例的代码:
var moq = new Mock<Foo>();
moq.Setup(m => m.GetAsync())
   .Returns(Task.FromResult(10));

在拥有更新版本的Moq的项目中,可以这样做。
var moq = new Mock<Foo>();
moq.Setup(m => m.GetAsync())
   .ReturnsAsync(10);

看一下Moq的ReturnsAsync实现:

public static IReturnsResult<TMock> ReturnsAsync<TMock, TResult>(this IReturns<TMock, Task<TResult>> mock, TResult value) where TMock : class
{
  TaskCompletionSource<TResult> completionSource = new TaskCompletionSource<TResult>();
  completionSource.SetResult(value);
  return mock.Returns(completionSource.Task);
}

两种方法在实现上似乎是相同的。两者都创建一个TaskCompletionSource,调用SetResult并返回Task
到目前为止都很好。
但是短暂运行的async方法被优化为同步操作。这似乎意味着TaskCompletionSource总是同步的,这也似乎表明上下文处理和任何相关问题都不会发生。
因此,如果我们有一些代码正在执行一些async禁忌,比如混合使用awaitsWait()Result,那么这些问题将无法在单元测试中被检测到。
是否有创建一个始终让出控制的扩展方法的优势?类似于:
public async Task<T> ReturnsYieldingAsync<T>(T result)
{
    await Task.Yield();
    return result;
}

在这种情况下,我们将拥有一个保证异步执行的方法。
其优点在于可以检测到不良的异步代码。例如,在单元测试期间,它可以捕获任何死锁或异常吞噬。
我不确定是否完全正确,因此我非常想听听社区的意见。

我会建议使用ReturnsYieldingAsync,但更进一步地,为了体验潜在的死锁情况,你需要在测试运行器中安装一个同步上下文,例如AsyncPump - noseratio - open to work
我认为你应该尝试编写一个测试用例,证明以下两点:1.它将按预期工作;2.你可以创建一个遵循第一项测试用例的测试用例,但从未调用“ReturnsYieldingAsync”并失败。 - David Pine
1
@Noseratio,关于 AsyncPump 的发现不错。我认为我们遇到的主要问题是测试运行器的同步上下文为空。根据这里所示的方法https://dev59.com/l2zXa4cB1Zd3GeqPVqBQ,替换 AsyncPump 似乎是解决问题的方法。 - swestner
@DavidPine 我同意,我们一直在尝试创建一个测试用例来触发死锁。上面的代码有点天真。它实际上会在进入链时立即等待。我们最终不得不通过 Moq 的 Callback 方法链接一个任务,然后在回调中使用的任务上调用 Return。即使这样,我们仍然没有遇到任何死锁。正如上面的评论所建议的那样,这可能是 AsyncPump 的问题,我现在正在测试不同的变化。当我把它全部解决了,我会更新问题或添加答案。 - swestner
1个回答

3

四年前我刚开始谈论关于测试异步代码, 我会鼓励开发人员为他们的模拟数据进行异步轴上的测试。也就是说,除了测试结果轴(成功、失败)外,还要测试异步轴(同步、异步)。

然而随着时间的推移,我对此变得更加宽松。当涉及到测试我的自己的代码时,我只测试同步成功/失败路径,除非有明显的原因需要测试该代码的异步成功路径。我不再费心测试异步失败情况。


在我们的情况下,我们遇到了许多混合异步模式的实例。在很大程度上,这是一个培训问题,但这也是我们不能忽视的问题。您的Nito库帮助我们一致地重现死锁。谢谢!不过,最终我们可能会通过端到端测试来测试这些类型的问题。 - swestner
我有一个非常好奇的问题,这也是最初提出的问题的核心。您知道TaskCompletionSource是否总是同步运行吗?这是最初问题的一个假设,如果知道就更好了。 - swestner
如果您调用了 SetResult(或类似方法),那么在方法调用返回之前,该任务已经完成。但是,可能仍然存在尚未完成的该任务的后续操作。 - Stephen Cleary

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接