單元測試(unit testing),是指對軟體中的最小可測試單元(函數/模塊/類)進行檢查和驗證。
單元測試是在軟體開發過程中要進行的最低級別的測試活動,軟體的獨立單元將在與程序的其他部分相隔離的情況下進行測試。
單元測試從長期來看,可以提高代碼質量,減少維護成本,降低重構難度。但是從短期來看,加大了工作量,對於進度緊張的項目中的開發人員來說,可能會成為不少的負擔。
單元測試應該遵循以下原則:
我們調研了以下開源C#單元測試工具(開源工具數據來自於GitHub):
C#單元測試相關的開源軟體中,NUnit及XUnit.NET星級排名靠前,MsTest是微軟公司開發的集成在Visual Studio中的C#單元測試工具,所以本文選取了星級排名前兩名的NUnit、XUnit.NET和MsTest。
對C#單元測試工具進行測評主要從功能性及非功能性兩部分來進行。其中,功能性測評中包括是否支持測試用例分類、排序等;非功能測評點包括社區活躍度及文檔完備性等。
框架介紹
(1) 基本介紹
MSTest是一款由微軟公司開發的單元測試框架,它能夠很好地被應用在Visual Studio中,並且集成在了Visual Studio單元測試框架中,操作簡單,上手容易。
從使用的角度來看,如果用戶使用的是Visual Studio作為IDE,那麼MSTest在對它的集成方面無疑是最方便的,無需下載,無需安裝,內置在vs的測試框架模板中。在VS中使用MsTest生成測試項目和新建一個C#項目一樣方便;如果用戶不使用VS,那麼也可以通過命令行執行.exe文件來執行單元測試,但是MsTest不提供自己單獨的GUI界面。
MsTest中核心的概念有Test Class(測試類)、Test Method測試方法、斷言和初始化及清理方法。
Test Class:通過使用[TestClass]屬性裝飾類來聲明測試類。該屬性用於標識包含測試方法的類,最佳做法規定測試類應僅包含單元測試代碼。
Test Method:通過使用[TestMethod]屬性裝飾單元測試方法來聲明測試方法。該屬性用於標識包含單元測試代碼的方法,最佳實踐指出,單元測試方法應僅包含單元測試代碼。
斷言:斷言是一段代碼,當運行於測試一個條件或行為針對預期的結果。通過調用Assert類中的方法來執行。
初始化和清理方法:初始化和清理方法用於在運行之前準備單元測試,並在執行單元測試之後進行清除。初始化方法通過用[TestInitialize]屬性裝飾初始化方法來聲明,而清理方法通過用[TestCleanup]屬性聲明清理方法。
許多時候會把VsTest和MsTest混淆,其實這兩個概念之間還是有一定區別的。
VsTest是Visual Studio測試平臺(Visual Studio Test Platform)的簡稱,是一個開放且可擴展的測試平臺,可用於運行測試,收集診斷數據和報告結果。VsTest支持運行在各種測試框架中並使用可插拔適配器模型編寫的測試。根據用戶的選擇,所需的測試框架及其相應的適配器可以視情況以vsix或NuGet軟體包的形式獲取。可以使用測試平臺公開的公共API來編寫適配器。
MsTest是指微軟開發的單元測試框架。目前MsTest最新的版本是MsTest V2,V2的版本依賴於兩個包:MSTest.TestFramework和MSTest.TestAdapter,用戶在使用時可以通過Nuget下載這兩個包來使用MsTest進行單元測試。MsTest V2主要是為了.net core準備的,也可以在.net framework上運行,並且較V1版本新加入了一些擴展。
下圖是VsTest的總體架構圖,從圖中可以看到,整個VsTest體系架構主要有四個組件:測試運行器、測試執行器、數據收集器和IDE。
其中,測試執行器部分主要包含一個執行測試的引擎,它把發現和執行測試的責任委託給了一些可擴展的測試適配器,允許測試平臺基於第三方測試框架發現/運行測試。這將通過相應框架的適配器來完成,該適配器了解如何在該框架中定義測試,並可以運行它們以向測試平臺提供結果。例如,MsTest適配器了解MsTest框架中編寫的測試,VsTest中就會使用MsTest適配器發現並執行它們。
在圖中還可以看到,測試運行器部分還包含一個VsTest console,其作用是使用命令行運行不同框架的測試。但是如果是採用MsTest作為測試框架,也可以使用MsTest.exe作為命令行執行的工具。
(2) 工具特點
支持為測試用例設置分類,執行時執行指定分類的測試方法:[TestCategory]
支持在一個或多個測試方法執行前後進行相關的準備、清理活動:[TestInitialize]和[TestCleanup]
支持為測試用例設置參數:[DataRow]
提供斷言方法,判定期望值和實際值是否一致
支持使用斷言等方式,對返回異常的測試用例進行異常判斷:[ExpectedException]
框架介紹
(1)基本介紹
NUnit 是專門針對於.NET 的自動化單元測試框架,是 XUnit 家族的一個成員,最初是由Java的單元測是框架JUnit 而來,作者最終用C#對其進行重新編寫,NUnit完全由C# 編寫,使其更加符合C#習慣,並充分利用了.NET中反射、客戶屬性等特性。因此,該工具具有豐富的單元測試歷史的同時,也具有適當的C#風格。
由於其獨立的歷史,NUnit還具有與其他工具良好交互的特點,並且在支持多種平臺,包括:.NET Core、Xamarin Mobile、Compact Framework及Silverlight等。NUnit在快速測試運行方面也享有盛譽,並且還具有一些不錯的附加功能,例如允指定給定測試的多個輸入等。
NUnit採用分層體系架構,主要有三層:測試運行器層(Test Runner)、測試引擎層(Test Engine)和框架層(Framework),其中,Test Runner層主要包含各種運行程序,包括獨立程序和在其它程序下運行的任務或者插件;Test Engine層則是NUnit平臺的核心,它提供公共API,供希望查找,加載和運行測試並顯示測試結果的應用程式使用;Framework層主要是為了兼容各個版本的NUnit程序。
NUnit中主要有三個抽象類:TestFixtureBuilderAttribute、TestCaseBuilderAttribute和IncludeExcludeAttribute。
TestFixtureBuilderAttribute是任何知道如何從所提供的類中構建某種測試fixture的屬性的基類,testfixture是指基於用戶類的任何測試。
TestCaseBuilderAttribute是任何知道如何從給定方法構建測試用例的屬性的基類。測試用例可以是簡單的(沒有參數)也可以是參數化的(接受參數),並且總是基於MethodInfo。
IncludeExcludeAttribute是任何用於根據字符串屬性include、exclude和Reason來決定是在當前運行中包含測試還是排除的屬性的基類,抽象類是使這些屬性可供派生類使用,派生類負責對它們採取操作。
在使用方面,NUnit可以通過控制臺或自己獨立的GUI來運行,在使用Visual Studio作為IDE時,NUnit也提供了相應的適配器,可以更好地和Visual Studio搭配使用。
(2)工具特點
NUnit2中有包含GUI界面;
支持為測試用例設置分類,執行時執行指定分類的測試方法:使用[Category]屬性;
支持在一個或多個測試方法執行前後進行相關的準備、清理活動:[SetUp]和[TearDown];
提供斷言方法,如果斷言失敗,則方法調用不會有值返回並報告錯誤。如果一個測試包含多個斷言,那麼在某次斷言失敗之後就終止,其後的任何斷言都不會執行;
支持為測試用例設置參數:[TestCase]
圖2-3 NUnit帶參測試方法實例
• 可以指定測試用例的執行順序:[Order]
• 支持使用斷言等方式,對返回異常的測試用例進行異常判斷:Assert.That
圖2-4 NUnit排序及異常判斷方法實例
支持通過註解等方式跳過執行帶有該註解的測試用例:[Ignore("Method is ignored")]
支持設置超時時間,[Timeout]、[MaxTime]。[MaxTime] 標記測試用例的最大執行時間,超時時報錯但不取消測試;[Timeout] 標記測試用例的超時時間,超時中斷測試
TestSuite :UNIT3之後取消該屬性,因為namespace也可以實現相同的功能
支持對對接主流的代碼覆蓋率工具,執行完單元測試用例後自動生成覆蓋率報告。(ncover)
框架介紹
(1)基本介紹
XUnit .NET是一個開源的的單元測試工具,由NUnit v2的原始發明者編寫,支持C#,F#,VB.NET版以及其他.NET語言,由.NET基金會支持,它採用了一種非常獨特、現代和靈活的單元測試方法。
XUnit .NET強調編寫具有較高的可讀性,簡單性的單元測試,與其它單元測試框架相比,有一些獨特的地方:
圖2-5 XUnit.NET帶參測試方法實例
XUnit支持更好地進行隔離測試。與其它測試框架不同,在xUnit中,每個測試方法運行後都會進行實例化操作,執行後釋放相應的空間,測試之間更加獨立,可以以任何順序執行測試,而不必擔心一個測試對其他測試的影響,消除了不同測試方法之間的依賴性。
XUnit取消了[SetUp]和[TearDown]方法,而採用構造函數進行初始化,使用IDisposable進行測試類的後處理等操作,讓每個測試對其需要的內容進行初始化。
(2) 工具特點
支持為測試用例設置分類,執行時執行指定分類的測試方法:[Trait("Category","UI")];
支持為測試用例設置參數:[Theory] [InilineData];
支持使用斷言等方式,對返回異常的測試用例進行異常判斷:Assert.Throws.Exception,長期使用[ExpectedException]會發現各種問題。首先,它沒有具體說明應該在哪一行代碼中引發異常,這會導致微妙且難以跟蹤的失敗,這些失敗會在通過測試時顯示出來。其次,由於處理不在測試的常規代碼流程之內,因此它沒有提供機會全面檢查異常本身的詳細信息。Assert.Throws允許您測試一組特定的代碼以引發異常,並在成功期間返回異常,以便您可以針對異常實例本身編寫進一步的斷言
本節採用一段簡易貨幣轉換計算器代碼作為被測代碼來試用三款工具,其測試用例基本可以涵蓋日常單元測試所需的功能。在試用過程中,同時採用了Moq框架及dotCover覆蓋率掃描工具。
主要有兩個接口:ICaculator和IMoneyEx,和一個ICaculator接口的實現類:Caculator。其功能是實現簡單運算及貨幣匯率轉換功能。
ICaculator接口定義如下:
public interface ICalculator { int Add(int param1, int param2); int Subtract(int param1, int param2); int Multipy(int param1, int param2); double Divide(int param1, int param2); int ConvertUSDtoRMB(int unit); }IMoneyEx接口定義如下:
public interface IMoneyEx { int GetActualUSDValue();}