当我分析Visual Studio 2012中的代码覆盖率时,async方法中的任何await行都显示为未被覆盖,即使它们在我的测试通过后显然执行。代码覆盖率报告说,未覆盖的方法是MoveNext
,这是不存在在我的代码(也许这是编译器生成的)。异步方法的代码覆盖率
有没有办法解决了异步方法的代码覆盖率报告?
注意:
我只是跑覆盖使用NCover,并覆盖人数,使用此工具更有意义。作为现在的解决方法,我将切换到此。
当我分析Visual Studio 2012中的代码覆盖率时,async方法中的任何await行都显示为未被覆盖,即使它们在我的测试通过后显然执行。代码覆盖率报告说,未覆盖的方法是MoveNext
,这是不存在在我的代码(也许这是编译器生成的)。异步方法的代码覆盖率
有没有办法解决了异步方法的代码覆盖率报告?
注意:
我只是跑覆盖使用NCover,并覆盖人数,使用此工具更有意义。作为现在的解决方法,我将切换到此。
这可如果它等待完成之前你在等待手术最常见的发生。
我建议您测试至少同步和异步成功的情况,但测试同步和异步错误和取消也是一个好主意。
我创建了一个测试运行器,它多次运行一段代码并改变使用工厂延迟的任务。这非常适合通过简单的代码块测试不同的路径。对于更复杂的路径,您可能需要为每个路径创建一个测试。
[TestMethod]
public async Task ShouldTestAsync()
{
await AsyncTestRunner.RunTest(async taskFactory =>
{
this.apiRestClient.GetAsync<List<Item1>>(NullString).ReturnsForAnyArgs(taskFactory.Result(new List<Item1>()));
this.apiRestClient.GetAsync<List<Item2>>(NullString).ReturnsForAnyArgs(taskFactory.Result(new List<Item2>()));
var items = await this.apiController.GetAsync();
this.apiRestClient.Received().GetAsync<List<Item1>>(Url1).IgnoreAwait();
this.apiRestClient.Received().GetAsync<List<Item2>>(Url2).IgnoreAwait();
Assert.AreEqual(0, items.Count(), "Zero items should be returned.");
});
}
public static class AsyncTestRunner
{
public static async Task RunTest(Func<ITestTaskFactory, Task> test)
{
var testTaskFactory = new TestTaskFactory();
while (testTaskFactory.NextTestRun())
{
await test(testTaskFactory);
}
}
}
public class TestTaskFactory : ITestTaskFactory
{
public TestTaskFactory()
{
this.firstRun = true;
this.totalTasks = 0;
this.currentTestRun = -1; // Start at -1 so it will go to 0 for first run.
this.currentTaskNumber = 0;
}
public bool NextTestRun()
{
// Use final task number as total tasks.
this.totalTasks = this.currentTaskNumber;
// Always return has next as turn for for first run, and when we have not yet delayed all tasks.
// We need one more test run that tasks for if they all run sync.
var hasNext = this.firstRun || this.currentTestRun <= this.totalTasks;
// Go to next run so we know what task should be delayed,
// and then reset the current task number so we start over.
this.currentTestRun++;
this.currentTaskNumber = 0;
this.firstRun = false;
return hasNext;
}
public async Task<T> Result<T>(T value, int delayInMilliseconds = DefaultDelay)
{
if (this.TaskShouldBeDelayed())
{
await Task.Delay(delayInMilliseconds);
}
return value;
}
private bool TaskShouldBeDelayed()
{
var result = this.currentTaskNumber == this.currentTestRun - 1;
this.currentTaskNumber++;
return result;
}
public async Task VoidResult(int delayInMilliseconds = DefaultDelay)
{
// If the task number we are on matches the test run,
// make it delayed so we can cycle through them.
// Otherwise this task will be complete when it is reached.
if (this.TaskShouldBeDelayed())
{
await Task.Delay(delayInMilliseconds);
}
}
public async Task<T> FromResult<T>(T value, int delayInMilliseconds = DefaultDelay)
{
if (this.TaskShouldBeDelayed())
{
await Task.Delay(delayInMilliseconds);
}
return value;
}
}
还有,我不关心测试方法的异步性质,但只是想摆脱的部分代码覆盖率的情况。我使用下面的扩展方法来避免这种情况,它对我来说工作得很好。
警告“Thread.Sleep”在这里使用!
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);
}
和使用类似于起订量的ReturnsAsync设置。
_sampleMock.Setup(s => s.SampleMethodAsync()).ReturnsAsyncDelayed(response);
代码未显示为被覆盖的原因与如何实现异步方法有关。 C#编译器实际上在转换异步方法的代码转换成实现一个状态机一类,并且将原始方法成初始化并调用该状态机存根。由于此代码是在您的程序集中生成的,因此它包含在代码覆盖率分析中。
如果您在执行覆盖的代码时使用的任务未完成,则编译器生成的状态机会挂起一个完成回调,以在任务完成时恢复。这更完全地执行状态机代码,并导致完整的代码覆盖(至少对于语句级代码覆盖工具)。
获得目前尚未完成的任务的常用方法是在单元测试中使用Task.Delay。然而,这通常是一个糟糕的选择,因为时间延迟太小(并且导致不可预知的代码覆盖率,因为有时任务在代码运行之前完成)或太大(不必要地减慢了测试的速度)。
一个更好的选择是使用 “等待Task.Yield()”。这将立即返回,但一旦设置就立即调用延续。
另一种选择 - 尽管有点荒谬 - 是实现你自己的等待模式,它具有报告不完整的语义,直到连续回调被连接,然后立即完成。这基本上强制状态机进入异步路径,提供完整的覆盖。
可以肯定,这不是一个完美的解决方案。最不幸的是它需要修改生产代码来解决工具的局限性。我更喜欢代码覆盖工具忽略编译器生成的异步状态机的部分。但是在这种情况发生之前,如果你真的想尝试获得完整的代码覆盖率,那么没有太多的选择。
一个这个技巧的更完整的解释可以在这里找到:http://blogs.msdn.com/b/dwayneneed/archive/2014/11/17/code-coverage-with-async-await.aspx
的方法都完成,并测试合格。它看起来像我遇到了工具的限制。 – Jacob 2013-03-24 22:17:00
没错,但是在await这个位置已经完成了操作吗? – 2013-03-24 22:52:12
难题...所以你真的不得不为每个await实例测试这些场景吗?如果你有一个有5个等待的方法,你必须写至少15个测试用例才能获得100%的覆盖率?这对我来说似乎是一个错误。这对我来说更像是测试编译器发出的异步机制,而不是测试自己的代码。 – Jacob 2013-03-24 23:25:05