Moles是前陣子91大推薦我用的Mock的Framework,它跟Moq與Rhino.Mocks這類的套件不同,Moq與Rhino.Mocks這類的Mock是對Interface或Abstract Class做Mock,而Moles是Mock整個CLR(Common Language Runtime),破除Moq等Mock套件的限制,非Interface或Abstract Class也可以Mock,而且還可以對Static Method來Mock,如DateTime.Now或File.Open等等Static Method,使原本在開發時不用為了方便測試而過度設計(如小弟之前為了方便測試,把DateTime又加了一層DateTimeProvider,當我看到這Framework後,就覺得我之前的作法非常的無聊,可參考Unit Test小技巧 : DateTime的Stub)。
Moles是前陣子91大推薦我用的Mock的Framework,它跟Moq與Rhino.Mocks這類的套件不同,Moq與Rhino.Mocks這類的Mock是對Interface或Abstract Class做Mock,而Moles是Mock整個CLR(Common Language Runtime),破除Moq等Mock套件的限制,非Interface或Abstract Class也可以Mock,而且還可以對Static Method來Mock,如DateTime.Now或File.Open等等Static Method,使原本在開發時不用為了方便測試而過度設計(如小弟之前為了方便測試,把DateTime又加了一層DateTimeProvider,當我看到這Framework後,就覺得我之前的作法非常的無聊,可參考Unit Test小技巧 : DateTime的Stub)。
•什麼是Mock
Mock簡單來說就是作一個假的東西,通常用在單元測試,因為寫單元測試時,難免沒辦法抽的很乾淨,多少會呼叫到別的Method,如寫訂單的處理,其中有呼叫到信用卡API,在寫單元測試不需要也盡可能不要呼叫到信用卡API,所以用Mock把信用卡API變成假的信用卡API,讓訂單的測試可以獨立,Mock的用法與概念當然不只這樣,詳情可以參考:
- Will保哥的ASP.NET MVC 單元測試系列
- In 91的測試系列
NOTE:
Mock與Stub都是用來作假物件,只是二個有實作上的差異,簡單來說,Mock是用動態產生的(如用Moq產生),Stub是已經寫好的(如為了測試實作一個Interface),本文統一都叫作Mock,雖然Moles感覺比較像Stub。
•Moq與Moles比較
Moq的Mock原理是動態產生Class,所以它才只能對Interface或Abstract Class,這類可繼承的型別,如對Interface就是動態產生一個實作Interface所有成員的Class,在用Setup來指定成員的內容或回傳值,多半會搭配IoC,讓程式在呼叫其他類別時,改為呼叫Mock的程式。
NOTE:
大部分Mock的Framework如Moq與Rhino Mocks的底層都是使用Castle DynamicProxy,來動態產生Class。
Moles的Mock原理是,在CLR上在建立一個自己的Runtime,測試時是在MoleRuntime上執行,所以原本是直接呼叫如DateTime.Now,改為呼叫Mock的程式。
圖一 MoleRuntime的假想圖
Moq與Moles都有適合的使用時機與限制,可參考圖二。
圖二 使用Mole與一般Mock的差異 來源:Microsoft Moles Reference Manual
•安裝Moles
Moles
Visual Studio 2010 Moles - Isolation Framework for .NET(x86)
Visual Studio 2010 Moles - Isolation Framework for .NET(x64)
Pex And Moles
Pex - Automated Whitebox Testing for .NET(x86)
x64版目前只在MSDN Suscribers中下載
NOTE:
Pex是一個Code的分析工具,可以產生相關的單元測試,都是同一個團隊開發,他們把二套Framework綁在一起,個人試用的結果,很有趣,但實用性不高。
•實作DateTime.Now的Mock
1.新增測試專案
2.在測試專案中的參考上按右鍵,選Add Moles Assembly for mscorlib,產生mscorlib.moles檔
圖三 新增mscorlib.moles檔
當專案中有.moles檔時,編譯會產生其對應的組件
圖四 mscorlib.moles檔與其對應的組件
NOTE:
在舊版的Moles中,mscorlib.moles檔的新增是在新增項目時自行輸入組件名稱,新版直接在參考中右鍵新增*.moles,方便很多。
圖五 新增.moles已取消從新增項目中新增的方式,因為找不到圖了借用程湘大的圖片一用。
mscorlib是.Net Framework是最基本的函式庫,俗稱Base Class Library(BCL),也就是把專案中的所有參考都刪除,剩下可以用的型別就是在mscorlib.dll中的型別,DateTime也是其一,而所有不同類型專案的mscorlib.dll一定都相同,如WebApplication與Silverlight的mscorlib.dll是一致的(這小弟我沒記錯是這樣,如果說錯了請見諒)。
如果想要Mock其他組件在其他組件上按右鍵新增該組件的.moles檔。
圖六 新增其他Moles組件
3.於TestMethod中撰寫DateTime的測試,且Mock DateTime並驗證。
[HostType("Moles")]
public void TestMethod1()
{
System.Moles.MDateTime.NowGet = () => { return new DateTime(2010, 1, 1); };
Assert.AreEqual(new DateTime(2010, 1, 1), DateTime.Now);
}
在TestMethod上增加[HostType("Moles")],讓Visual Studio知道這一個TestMethod,是要用MoleRuntime執行,Mock的方式是用Delegate,如圖四Moles會建立mscorlib的Mock用組件,其邏輯如下:
- Namespace:最後加上Moles
- Type:在前加上M
- Property:後方加上Get或Set
- Method:後方加上參數的型別
- Instance:在AllInstances下操作
所以System.DateTime.Now的Mock在System.Moles.MDateTime.NowGet,範例中DateTime.Now就改為Mock的值,而不是現在時間。
4.增加MoledType宣告。
在檔案的最上方或AssemblyInfo.cs中加上MoledType的宣告,每一個要Mock的Type都要增加。
5.執行測試,並通過測試。
•已知問題
我在使用Moles版本0.94.51023.0時,有遇到一個問題,就是MoleRuntime無法正確取得測試專案的app.config,我也有在MSDN中詢問別人,得知的訊息是Bug,可參考Can't get data via ConfigurationManager.AppSettings on HostType("Moles"),但山不轉路轉,聰明的方法沒有,笨死人的解決方法到有,MoleRuntime只會抓自己的app.config,所以只要將資料寫在MoleRuntime的app.config中測試時就可以正常讀取資料,只是設定就沒辦法很方便的xCopy,且會所有專案共用一個app.config,最後我只想到切換專案時,手動更換檔案。
圖七 各測試專案需要的app.config
Moles的執行檔案路徑在
C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\PrivateAssemblies\Microsoft.Moles.VsHost.x86.exe
•其他範例
System.Moles.MGuid.NewGuid = () =>
{
return new Guid("FFEB1621-5016-4F28-94AE-5DB72FC8A23B");
};
//測試時不需要真的讀實體檔案
System.IO.Moles.MFile.ReadAllTextString = (filePath) =>
{
return "測試內容";
};
System.Net.Mail.Moles.MSmtpClient.AllInstances.SendMailMessage = (smtp, message) =>
{
//測試時不真的寄Email
};
•參考資料
Moles - Isolation framework for .NET