Visual studio 2012 异步方法的代码覆盖率
当我在Visual Studio 2012中分析代码覆盖率时,异步方法中的任何等待行都显示为未覆盖,即使它们显然正在执行,因为我的测试正在通过。代码覆盖率报告说,未覆盖的方法是Visual studio 2012 异步方法的代码覆盖率,visual-studio-2012,mstest,code-coverage,async-await,c#-5.0,Visual Studio 2012,Mstest,Code Coverage,Async Await,C# 5.0,当我在Visual Studio 2012中分析代码覆盖率时,异步方法中的任何等待行都显示为未覆盖,即使它们显然正在执行,因为我的测试正在通过。代码覆盖率报告说,未覆盖的方法是MoveNext,这在我的代码中不存在(可能是编译器生成的) 有没有办法修复异步方法的代码覆盖率报告 注意: 我只是使用NCover运行了覆盖率,使用该工具,覆盖率数字变得更有意义。作为目前的解决办法,我将切换到这一点。如果等待的操作在等待之前完成,则这种情况最常见 我建议您至少测试同步和异步成功情况,但测试同步和异步错误
MoveNext
,这在我的代码中不存在(可能是编译器生成的)
有没有办法修复异步方法的代码覆盖率报告
注意:
我只是使用NCover运行了覆盖率,使用该工具,覆盖率数字变得更有意义。作为目前的解决办法,我将切换到这一点。如果等待的操作在等待之前完成,则这种情况最常见
我建议您至少测试同步和异步成功情况,但测试同步和异步错误以及取消也是一个好主意。我创建了一个测试运行程序,可以多次运行代码块,并使用工厂更改延迟的任务。这非常适合通过简单的代码块测试不同的路径。对于更复杂的路径,您可能希望为每个路径创建一个测试
[TestMethod]
公共异步任务ShouldTestAsync()
{
等待AsyncTestRunner.RunTest(异步任务工厂=>
{
this.apiRestClient.GetAsync(null字符串).ReturnsForAnyArgs(taskFactory.Result(new List());
this.apiRestClient.GetAsync(null字符串).ReturnsForAnyArgs(taskFactory.Result(new List());
var items=wait this.apiController.GetAsync();
this.apiRestClient.Received().GetAsync(Url1.IgnoreAwait();
this.apiRestClient.Received().GetAsync(Url2.IgnoreAwait();
AreEqual(0,items.Count(),“应返回零项”);
});
}
公共静态类AsyncTestRunner
{
公共静态异步任务运行测试(Func测试)
{
var testTaskFactory=newtesttaskfactory();
while(testTaskFactory.nextestTrun())
{
等待测试(testTaskFactory);
}
}
}
公共类TestTaskFactory:ITestTaskFactory
{
公共TestTaskFactory()
{
this.firstRun=true;
this.totalTasks=0;
this.currentTestRun=-1;//从-1开始,因此第一次运行时将转到0。
此.currentTaskNumber=0;
}
公共bool nextesttrun()
{
//使用最终任务编号作为总任务。
this.totalTasks=this.currentTaskNumber;
//在我们还没有延迟所有任务时,总是返回下一个作为第一次运行的轮到。
//如果所有任务都运行sync,则我们还需要一个测试运行。
var hasNext=this.firstRun | | this.currentTestRun有些情况下,我不关心测试方法的异步性质,只是想摆脱部分代码覆盖。我使用下面的扩展方法来避免这种情况,它对我来说很好
警告“线程。睡眠”在这里使用
public static IReturnsResult<TClass> ReturnsAsyncDelayed<TClass, TResponse>(this ISetup<TClass, Task<TResponse>> setup, TResponse value) where TClass : class
{
var completionSource = new TaskCompletionSource<TResponse>();
Task.Run(() => { Thread.Sleep(200); completionSource.SetResult(value); });
return setup.Returns(completionSource.Task);
}
代码未显示为包含的原因与异步方法的实现方式有关。C#编译器实际上将异步方法中的代码转换为实现状态机的类,并将原始方法转换为一个存根,该存根初始化并调用该状态机。因为此代码是在assem中生成的当然,它包含在代码覆盖率分析中
如果您使用的任务在被覆盖的代码执行时尚未完成,编译器生成的状态机将挂起一个完成回调,以便在任务完成时继续。这将更完整地执行状态机代码,并导致完整的代码覆盖(至少对于语句级代码覆盖工具)
获取当前尚未完成但将在某个时间点完成的任务的常用方法是在单元测试中使用task.Delay。但是,这通常是一个较差的选项,因为时间延迟太小(并导致无法预测的代码覆盖率,因为有时任务在测试代码运行之前完成)或者太大(不必要地减慢测试)
更好的选择是使用“await Task.Yield()”。这将立即返回,但在设置后立即调用continuation
另一种选择——尽管有些荒谬——是实现您自己的等待模式,该模式的语义是在连接延续回调之前报告不完整,然后立即完成。这基本上迫使状态机进入异步路径,提供完整的覆盖
当然,这不是一个完美的解决方案。最不幸的是,它需要修改生产代码以解决工具的限制。我更希望代码覆盖率工具忽略由编译器生成的异步状态机的部分。但在这之前,如果y你真的想尝试获得完整的代码覆盖率
关于这一黑客行为的更完整的解释可以在这里找到:方法都在完成,测试也在通过。看起来我遇到了工具的限制。对,但是在await
的时候操作已经完成了吗?明白了……所以你真的必须为每个await实例测试这些场景?如果您有一个包含5个等待的方法,那么您必须编写至少15个测试用例才能获得100%的覆盖率?对我来说,这似乎是一个错误。对我来说,这更像是测试编译器发出的异步机制,而不是测试您自己的代码。我同意您不必测试异步机制,但有几种不同的执行路径在一个<代码>中等待“< /COD>”。所以请考虑方法的所有语义:如果等待的任务没有完成,是否应该完成?如果等待的任务已经完成,它是否应该同步完成?是否应该传播异常?一旦开始覆盖所有实际的语义,您可能会发现代码覆盖不是问题。异步库
_sampleMock.Setup(s => s.SampleMethodAsync()).ReturnsAsyncDelayed(response);