异步/等待单元测试代码覆盖率

10

如何编写用于 async/await 方法的单元测试,我正在使用 Visual Studio 2013。

假设我们有一个 async 方法:

public async Task DoSomethingAsync()
{
    ...
    await _service.DoInternalAsync();
    ...
}

由于我使用的是最新版本的Visual Studio,它对异步方法单元测试具有良好支持:

[TestMethod]
public async Task DoSomthingAsyncTest()
{
    ...
    await _objectUnderTest.DoSomethingAsync();
    // how to verify the result??? here is what I did
    _service.Verify(_ => _.DoInternalAsync());
}

基本上,我有两个问题:

  1. 如代码中所述,如何验证Task的结果?我这样做对吗?
  2. 如果我运行该测试,VS会说测试通过。但是当我检查代码覆盖率时,从代码覆盖率结果的视图来看,await _service.DoInternalAsync()语句似乎未被覆盖,它提示MoveNext()语句有6个未被覆盖的块。其中有什么问题?

4
由于您的async方法只返回一个任务,因此只需断言Service的模拟被调用即可。无需测试其他内容,您的service类中的单元测试已经覆盖了其行为。如果有返回值,可以使用一种方式来模拟具有异步方法的类。 - Pierre-Luc Pineault
2个回答

8

根据我的研究,代码覆盖率问题是Visual Studio 2013的一个Bug,在下一个主要版本中会进行修复/增强。

引用自反馈:

您所看到的问题是由我们端口的一个错误导致的,我们目前在代码覆盖方面没有完全支持async/await模式,这项工作尚未完成并将在下一个主要更新/发布中得到解决。对于这个问题,没有干净的解决方法。


1
感谢您告知我们官方反馈。 - Emile

4
代码没有被标记为覆盖的原因与异步方法的实现方式有关。C#编译器实际上将异步方法中的代码转换为实现状态机的类,并将原始方法转换为初始化和调用该状态机的存根。由于此代码在程序集中生成,因此它包含在代码覆盖分析中。
如果您在执行被覆盖代码时使用尚未完成的任务,则编译器生成的状态机会挂钩完成回调以在任务完成时恢复。这更完全地运行状态机代码,并导致完整的代码覆盖(至少对于语句级别的代码覆盖工具)。
获取当前不完整但最终会完成的任务的常见方法是在单元测试中使用Task.Delay。然而,这通常是一个不好的选择,因为时间延迟要么太小(结果是不可预测的代码覆盖,因为有时在测试运行之前任务已经完成),要么太大(不必要地减慢测试速度)。
更好的选择是使用“await Task.Yield()”。这将立即返回,但一旦设置就会调用后续处理程序。
另一个选项-虽然有些荒谬-是实现自己的可等待模式,其具有报告不完整的语义,直到连接回调,然后立即完成。这基本上将状态机强制进入异步路径,提供完整的覆盖范围。
当然,这不是一个完美的解决方案。最不幸的方面是它需要修改生产代码来解决工具的限制。我更喜欢代码覆盖工具忽略编译器生成的异步状态机部分。但在此之前,如果您真的想尝试获得完整的代码覆盖,则没有太多选择。
有关此技巧的更完整说明,请参见此处:http://blogs.msdn.com/b/dwayneneed/archive/2014/11/17/code-coverage-with-async-await.aspx

虽然这个链接可能回答了问题,但最好在此处包含答案的基本部分并提供参考链接。如果链接页面更改,仅有链接的答案可能会失效。 - Hashem Qolami
好的,我添加了选项摘要以及更深入文章的链接。 - Dwayne Need

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