10

Visual Studio 2012でコードカバレッジを分析すると、テストに合格しているため、明らかに実行されているにもかかわらず、非同期メソッドの待機行がカバーされていないことが示されています。コードカバレッジレポートには、カバーされていないメソッドがであると記載されていますがMoveNext、これは私のコードには存在しません(おそらくコンパイラによって生成されたものです)。

非同期メソッドのコードカバレッジレポートを修正する方法はありますか?

私はNCoverを使用してカバレッジを実行しましたが、カバレッジ数はそのツールを使用する方がはるかに理にかなっています。今のところ回避策として、それに切り替えます。

4

4 に答える 4

5

これは、待機している操作が待機する前に完了した場合に最も一般的に発生する可能性があります。

少なくとも同期と非同期の成功状況をテストすることをお勧めしますが、同期と非同期のエラーとキャンセルをテストすることもお勧めします。

于 2013-03-24T22:04:19.917 に答える
2

コードがカバーされていると表示されない理由は、非同期メソッドの実装方法に関係しています。C#コンパイラは、実際には非同期メソッドのコードをステートマシンを実装するクラスに変換し、元のメソッドを初期化してそのステートマシンを呼び出すスタブに変換します。このコードはアセンブリで生成されるため、コードカバレッジ分析に含まれます。

対象となるコードの実行時に完了していないタスクを使用する場合、コンパイラーによって生成されたステートマシンは、タスクの完了時に再開するために完了コールバックをフックします。これにより、ステートマシンコードがより完全に実行され、完全なコードカバレッジが実現します(少なくともステートメントレベルのコードカバレッジツールの場合)。

現時点では完了していないが、ある時点で完了するタスクを取得する一般的な方法は、単体テストでTask.Delayを使用することです。ただし、時間遅延が小さすぎる(テスト対象のコードが実行される前にタスクが完了することがあるため、コードカバレッジが予測できない)か、大きすぎる(テストの速度が不必要に遅くなる)ため、これは一般的に不適切なオプションです。

より良いオプションは、「await Task.Yield()」を使用することです。これはすぐに戻りますが、設定されるとすぐに継続を呼び出します。

もう1つのオプションは、多少ばかげていますが、継続コールバックがフックされるまでレポートが不完全であるというセマンティクスを持つ独自の待機可能なパターンを実装し、すぐに完了することです。これは基本的にステートマシンを非同期パスに強制し、完全なカバレッジを提供します。

確かに、これは完璧な解決策ではありません。最も不幸な点は、ツールの制限に対処するために製品コードを変更する必要があることです。コードカバレッジツールが、コンパイラによって生成された非同期ステートマシンの部分を無視することを強く望んでいます。しかし、それが起こるまで、本当に完全なコードカバレッジを取得しようとする場合、多くのオプションはありません。

このハッキングのより完全な説明はここで見つけることができます:http://blogs.msdn.com/b/dwayneneed/archive/2014/11/17/code-coverage-with-async-await.aspx

于 2014-11-19T05:07:46.183 に答える
1

メソッドの非同期性をテストすることを気にせず、部分的なコードカバレッジを取り除きたいだけの状況があります。私はこれを避けるために以下の拡張方法を使用します、そしてそれは私にとってうまく機能します。

警告「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);
}

使用法は、MoqのReturnsAsyncセットアップと同様です。

_sampleMock.Setup(s => s.SampleMethodAsync()).ReturnsAsyncDelayed(response);
于 2014-05-13T09:59:19.790 に答える
-1

コードのブロックを複数回実行し、ファクトリを使用して遅延するタスクを変更するテストランナーを作成しました。これは、単純なコードブロックを介してさまざまなパスをテストするのに最適です。より複雑なパスの場合は、パスごとにテストを作成することをお勧めします。

[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;
    }
}
于 2014-04-11T19:18:47.807 に答える