鑒于.NET Framework 4.5后.NET增加了對 async/await 的支持,本文討論的異步內(nèi)容均基于async/await
??客戶端界面開發(fā),多線程是逃不了的話題,而多線程的加入勢必對程序的穩(wěn)定性帶來挑戰(zhàn),單元測試就顯得更為重要。相對于同步代碼的測試,多線程單元測試有更多細(xì)節(jié)需要注意。
async void 和 async Task。
??假設(shè)某一天你運氣不好,需要為類似如下的方法補充單元測試:
public static bool Changed;
public static async void ChangeAsync()
{
await Task.Run(() =>
{
Task.Delay(1000);
Changed = true;
});
}
你發(fā)現(xiàn),要測試此方法需要用一些奇葩的方式,比如:
[TestMethod()]
public void ChangeAsyncTest_OriginalFalse_ChangeToTrue()
{
AsyncClient.Changed = false;
AsyncClient.ChangeAsync();
Thread.Sleep(1100);
Assert.IsTrue(AsyncClient.Changed);
}
顯然,這種延時等待是極其惡心的,如果ChangeAsync方法返回的不是void而是Task,我們就可以愉快的await了:
[TestMethod()]
public async Task ChangeAsyncTest_OriginalFalse_ChangeToTrue()
{
AsyncClient.Changed = false;
await AsyncClient.ChangeAsync();
Assert.IsTrue(AsyncClient.Changed);
}
需要特別注意的是,在異步單元測試方法中也必須返回Task,這是MSTest的約定,否則這個測試方法無法運行起來。(實際上MSTest也需要使用返回的Task來收集異常,關(guān)于這部分更多內(nèi)容可以參見Async/Await最佳實踐)
拋棄ExpectedException
??在測試程序是否按照預(yù)期的拋出了異常,我們常常會用ExpectedException,這家伙有一個問題,它是對整個測試方法的方法體做捕獲,也就是說測試方法中的非action代碼拋出了異常依然能夠被ExpectedException捕獲,這就造成潛在的bug,為了解決此問題,在MSTest V2之前往往需要寫一些輔助方法,但MSTest V2斷言庫中增加了Assert.ThrowsExceptionAsync和Assert.ThrowsException,可以精確的定位在哪段代碼中拋出了異常。假設(shè)我們的被測代碼跟下面類似:
public static async Task ChangeAsync()
{
await Task.Run(() =>
{
throw new InvalidOperationException();
});
}
測試代碼可以這樣寫:
[TestMethod()]
public async Task ChangeAsyncTest_ThrowInvalidOperationException()
{
await Assert.ThrowsExceptionAsync<InvalidOperationException>(async () =>
{
await AsyncClient.ChangeAsync();
});
}
異步方法mock
??在moq中,異步方法的mock也是極其簡單的,假設(shè)有這樣的接口:
public interface ITextReader
{
Task<string> ReadTextAsync();
}
測試代碼中mock其返回結(jié)果可以有如下兩種寫法:
var mockTextReader = new Mock<ITextReader>();
//可以這樣
mockTextReader.Setup(x => x.ReadTextAsync()).Returns(async ()=>await Task.FromResult("mockValue"));
//也可以這樣
mockTextReader.Setup(x => x.ReadTextAsync()).ReturnsAsync(()=> "mockValue");
2017-11-30 15:26:34