當我分析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