2013-03-24 41 views
9

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

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

私はNCoverを使用してカバレッジを走り、カバレッジ番号は、そのツールを使用して、より多くの意味をなします。今のところ回避策として、私はそれに切り替えるつもりです。

答えて

4

待機している操作が完了するまで待っている場合は、これが最も一般的です。

少なくとも同期と非同期の成功状況をテストすることをお勧めしますが、同期と非同期のエラーと取り消しをテストすることもお勧めします。

+1

方法がすべて完了しており、テストが合格しています。私はツールの限界に遭遇しているようです。 – Jacob

+0

右ですが、操作は 'await'の時点ですでに完了していますか? –

+0

Gotcha ...だから、待っているそれぞれのインスタンスのシナリオを実際にテストする必要がありますか? 5つの方法がある場合は、100%のカバレッジを得るために少なくとも15のテストケースを記述する必要があります。それは私のバグのようだ。コンパイラが発行した非同期メカニズムをテストするのは、自分自身のコードをテストするよりも、もっと魅力的です。 – Jacob

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

メソッドの非同期性をテストするのは気にしませんが、部分的なコードカバレッジを取り除きたい場合があります。私はこれを避けるために以下の拡張メソッドを使用し、それは私のためにうまく動作します。

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

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

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

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

"await Task.Yield()"を使用することをお勧めします。これは即座に戻りますが、設定されるとすぐに継続を呼び出します。

もう1つの選択肢は、継続コールバックが接続されるまで報告が完了しないと、すぐに完了するまで、独自の待ち受け可能なパターンを実装することです。これは、基本的に状態マシンを非同期パスに強制し、完全なカバレッジを提供します。

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

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

関連する問題