我们正在使用MSTest和Moq编写异步代码的单元测试。我们有一些类似以下示例的代码:
在拥有更新版本的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
禁忌,比如混合使用awaits
、Wait()
和Result
,那么这些问题将无法在单元测试中被检测到。是否有创建一个始终让出控制的扩展方法的优势?类似于:
public async Task<T> ReturnsYieldingAsync<T>(T result)
{
await Task.Yield();
return result;
}
在这种情况下,我们将拥有一个保证异步执行的方法。
其优点在于可以检测到不良的异步代码。例如,在单元测试期间,它可以捕获任何死锁或异常吞噬。
我不确定是否完全正确,因此我非常想听听社区的意见。
ReturnsYieldingAsync
,但更进一步地,为了体验潜在的死锁情况,你需要在测试运行器中安装一个同步上下文,例如AsyncPump
。 - noseratio - open to workAsyncPump
的发现不错。我认为我们遇到的主要问题是测试运行器的同步上下文为空。根据这里所示的方法https://dev59.com/l2zXa4cB1Zd3GeqPVqBQ,替换AsyncPump
似乎是解决问题的方法。 - swestnerCallback
方法链接一个任务,然后在回调中使用的任务上调用Return
。即使这样,我们仍然没有遇到任何死锁。正如上面的评论所建议的那样,这可能是AsyncPump
的问题,我现在正在测试不同的变化。当我把它全部解决了,我会更新问题或添加答案。 - swestner