(此文章同時發表在本人微信公眾號“dotNET每日精華文章”,歡迎右邊二維碼來關注。)
題記:雖然ABP為大家提供了測試的腳手架了,不過有些小技巧還是需要自己探索的。
ASP.NET Boilerplate(ABP)默認使用的單元測試的框架是xUnit,並且引入了NSubstitute這個便捷的Mock框架。但是有些具體的用法和技巧還是需要自己探索的。下面介紹兩個小技巧。
為AppService注入Mock接口
假設我有一個和釘釘相關的AppService(接口名稱為IDingtalkAppService),這個服務中會調用釘釘的sdk,為了在單元測試的時候避免真實的網絡調用,所以我需要把這部分代碼抽象為一個接口(比如名稱為IDingtalkSdkService)。那么如何把IDingtalkSdkService 模擬注入到IDingtalkAppService中呢?
首先,在TestModule的PreInitialize中添加如下代碼:
//Registering fake services IocManager.IocContainer.Register( Component.For<IAbpZeroDbMigrator>() .UsingFactoryMethod(() => Substitute.For<IAbpZeroDbMigrator>()) .LifestyleSingleton(), Component.For<IDingtalkSdkService>() .UsingFactoryMethod(() => Substitute.For<IDingtalkSdkService>()) .LifestyleSingleton() );
上面的代碼通過NSubstitute為IDingtalkSdkService注冊一個Mock。在測試類中,還是如ABP的慣例那樣,通過Resolve來獲取IDingtalkAppService的實現。只是在需要給Mock接口的方法設置Returns數據的時候,在測試方法中通過Resolve來現時獲取到此接口的實例。如下:
var sdkService = Resolve<IDingtalkSdkService>(); sdkService.GetAllUserList(null, enterprise).Returns(
這種方式雖然最簡單但有個缺陷,就是必須注冊的時候要求注冊為LifestyleSingleton(單例)。那么如果你依賴的外部接口無法以單例模式運行(這種情況應該極少),那么只有給IDingtalkAppService添加一個IDingtalkSdkService的屬性SdkService,然后把上面的代碼替換為appService.SdkService.GetAllUserList。
NSubstitute使用的更多幫助,請參考其官網:http://nsubstitute.github.io/
數據驅動的單元測試
准確說這不屬於ABP的技巧是xUnit的技巧,只是ABP用到了xUnit,就一並提及。我們的業務邏輯可能會涉及到多個場景或者判斷條件,為了測試每種情況,是為每種情況寫一個測試方法並包含大量的重復代碼,還是使用數據驅動注入測試數據到一個測試方法中,顯然最好的方式是后者。
常見的單元測試框架都提供了數據驅動的支持:
- xUnit:http://www.tomdupont.net/2012/04/xunit-theory-data-driven-unit-test.html
- NUnit:http://www.tomdupont.net/2014/06/nunit-testcase-data-driven-unit-test.html
- MSTest:https://msdn.microsoft.com/en-us/library/ms182527.aspx
就我的感受而言,xUnit處理數據驅動的方式更為自然。我采用了MemberData的方式來提供數據,就是寫一個返回值為IEnumerable<object[]>的靜態公共方法。
“不廢話,直接上代碼”,大家更能一目了然。示例代碼見:http://git.oschina.net/ike/xbonmpl9wav062rsj71y872.code.git
通過這種方式,我檢查出了一個判斷錯誤,如下圖: