代码覆盖率为异步方法(Code coverage for async methods)

2019-07-23 02:34发布

当我分析代码覆盖率在Visual Studio 2012年,任何异步方法的await线都显示为未覆盖,即使他们显然是执行,因为我的测试都通过。 代码覆盖率报告说,未覆盖的方法是MoveNext ,它不存在于我的代码(也许这是编译器生成的)。

有没有办法解决的代码覆盖率异步方法报告?

注意

我只是跑覆盖使用NCover,并覆盖人数,使用此工具更有意义。 至于现在要解决此问题,我会切换到这一点。

Answer 1:

这可如果它等待完成之前,你等待手术最常见的发生。

我建议你至少同步和异步成功的情况下进行测试,但它也是测试同步和异步错误和取消是一个好主意。



Answer 2:

代码并不显示为覆盖的原因与如何异步方法实现的事情。 C#编译器实际上在转换异步方法的代码转换成实现一个状态机一类,并且将原始方法成初始化并调用该状态机存根。 由于该代码在你的组件中产生的,它包含在代码覆盖分析。

如果您使用的任务,是不完整的代码被覆盖在执行时,编译器生成的状态机挂接完成回调,以恢复任务完成时。 这更完全行使国家机器代码,并导致完整的代码覆盖率(对于语句级代码覆盖工具至少)。

一种常见的方式来获得任务未完成的那一刻,但将完成在某些时候是在你的单元测试使用Task.Delay。 然而,这通常是一个糟糕的选择,因为时间延迟要么太小(并导致不可预知的代码覆盖率,因为有时在任务完成的代码为测试运行之前)或太大(不必要地减缓下来测试)。

更好的选择是使用“等待Task.Yield()”。 这将立即返回,但因为它被设置为尽快调用延续。

另一种选择 - 虽然有点荒谬 - 是实现具有直至延续回调挂钩报告不完整的语义,然后立即完成自己awaitable模式。 这基本上迫使状态机进入异步路径,提供完整的覆盖。

可以肯定,这不是一个完美的解决方案。 最不幸的方面是,它需要修改生产代码以处理工具的限制。 我更希望的是,代码覆盖工具会忽略由编译器生成的异步状态机的部分。 但是,直到出现这种情况,不会有太多的选择,如果你真的想尝试获得完整的代码覆盖率。

这个技巧的更完整的解释可以在这里找到: http://blogs.msdn.com/b/dwayneneed/archive/2014/11/17/code-coverage-with-async-await.aspx



Answer 3:

还有,我不关心测试方法的异步性质,但只是想摆脱的部分代码覆盖率的情况。 我用下面的扩展方法来避免这个和它的作品对我蛮好。

警告“ 的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);


Answer 4:

我创建运行的代码多次的块并改变被使用工厂延迟该任务的测试运行。 这是伟大的通过代码的简单块测试不同的路径。 对于更复杂的路径,你可能要创建每个路径的测试。

[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;
    }
}


文章来源: Code coverage for async methods