在多次的文章中討論到可測試性。為了增加 method 的可測試性,往往需要更改 method 的參數,讓不具可測試性的 method,抽離出可測試的部份。
問題
靜態函式往往是最不具測試性的原兇。例如 DateTime.Now, File.Exists 等。為了這些障礙,我們必須一次又一次的拆解我們原本良好的 method 嗎?
舉例
public class MonthHelper { /// <summary> /// 今天是否為單數月 /// </summary> /// <returns></returns> public bool IsOddNumberMonth() { return DateTime.Today.Month%2 == 1; } }
DateTime.Today 會隨著電腦時間而變,IsOddNumberMonth 也會因執行時間而回傳不同的結果,因此該 method 不具可測試性。為了可測試性,之前教大家要將此程式拆成兩部份。如下
public class MonthHelper { /// <summary> /// 今天是否為單數月 /// </summary> /// <returns></returns> public bool IsOddNumberMonth() { return IsOddNumberMonth(DateTime.Today); } /// <summary> /// 是否為單數月 /// </summary> /// <returns></returns> public bool IsOddNumberMonth(DateTime dateTime) { return dateTime.Month % 2 == 1; } }
這樣,我們就可以對 IsOddNumberMonth(DateTime) 進行測試。而原本的IsOddNumberMonth() 就只好放棄了。
問題在於,如此簡單的 method,卻必須為了可測試性拆成兩個 method,不太合理。萬一有個 method 裡有一堆不可測試的 method,如 DateTime.Now, File.Exists, File.ReadAllLines 等,我們需要拆解多少次才具可測試性?這樣的程式具可讀性嗎?
Moles
Moq 是非常棒的 Isolation framework,但只能對 interface 或 virtual method 進行 mock,對於靜態函式仍莫可奈何。
對此問題,微軟的 Microsoft Research 提出Moles 這一個 Isolation framework。其原理在於對整個 .NET Framework 的 mock,而非單一類別/方法的 mock。
以上例來說,我們可以進行下來步驟進行單元測試撰寫。(記得先下載及安裝 Moles)
1. 建立測試專案
在該 method 上按右鍵,執行「Create Unit Tests」
2. 加入 Moles 項目
在剛剛建立的測試專案上增加一個項目,選到 「Moles and Stubs for Testing」。此時項目的名稱必須與要模擬的 assembly 名稱一致。由於 DateTime 這個 type 的assembly 是 mscorlib 中,(見 DateTime 結構),所以名稱必須取作 mscorlib.moles。按 Add鍵到下一步驟。
3. 建立測試
在測試程式的最上頭,增加 [assembly: MoledType(typeof(System.DateTime))]。這一行說明 Moles 的 runtime 要對 System.DateTime 進行模擬。
修改測試如下。[HostType(“Moles”)] 告訴測試環境是跑在 Moles 的環境下。而 MDateTime 則是對 DateTime 的模擬。Moles 對原來的 dotnet framework 的模擬,都使用固定的命名方式。MDateTime 就是對 DateTime 的 mock type. 屬性的 mock也區分 Get 與 Set。MDateTime.TodayGet 是對 DateTime.Today getter 的 mock。
4. 執行測試
以下是完整的測試內容。當然,測試結果都是成功的
using System.Moles; using ConsoleApplication1; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using Microsoft.Moles.Framework; [assembly: MoledType(typeof(System.DateTime))] namespace TestProject1 { [TestClass()] public class MonthHelperTest { [TestMethod()] [HostType("Moles")] public void IsOddNumberMonth_Even_ReturnFalse() { MDateTime.TodayGet = () => new DateTime(2010, 12, 31); var target = new MonthHelper(); bool actual = target.IsOddNumberMonth(); Assert.AreEqual(false, actual); } [TestMethod()] [HostType("Moles")] public void IsOddNumberMonth_Odd_ReturnTrue() { MDateTime.TodayGet = () => new DateTime(2010, 11, 30); var target = new MonthHelper(); bool actual = target.IsOddNumberMonth(); Assert.AreEqual(true, actual); } } }
結論
怎樣,相當棒吧。Moq 是我最常用的 isolation framework,執行起來會快很多。而當需要模擬 dotnet framework 的靜態方法時,我就會用 Moles。