原位地址:http://codetunnel.com/blog/post/what-is-a-mocking-framework-why-is-it-useful
今天我想講下關於mocking frameworks,並且解釋下他為什麼有用處。我將給你們展示用和不用mocking framework兩種測試方法。
假設我們已經有了一個Driver類:
public class Driver{ private IVehicle vehicleToDrive; public Driver(IVehicle vehicleToDrive) { this.vehicleToDrive = vehicleToDrive; } public bool EvasiveManeuvers(bool alertOffendingDriver) { bool success = false; if (alertOffendingDriver) success = this.vehicleToDrive.ApplyBrakes() && this.vehicleToDrive.HonkHorn(); else success = this.vehicleToDrive.ApplyBrakes(); return success; }}
【注意】Driver類的建構函式依賴IVehicle介面!定義如下:
public interface IVehicle{ ///<summary> ///Honks the vehicle's horn. ///</summary> ///<returns> ///True if the action was successful. ///</returns> bool HonkHorn(); ///<summary> ///Applies the vehicle's brakes. ///</summary> ///<returns> ///True if the action was successful. ///</returns> bool ApplyBrakes();}
現在我們需要寫2個單元測試來測試Driver。然而在我們寫之前,必須能夠通過Driver的建構函式才行,他依賴IVehicle介面。
很多人認為只要隨便實現IVehicle就可以了,這當然沒問題。事實上,如果你完成介面IVechicle,你也不用寫單元測試了。
單元測試是孤立性的,測試Driver類並且還要實現一個完整IVehicle實現不是單元測試,那是整體封閉測試。如果你在測試時出錯了,你不能確認是Driver出錯了還是其他的類。
我們現在搞一個假的IVehicle的實現來解決這個依賴問題。
public class FakeVehicle : IVehicle{ public int CalledHonkHorn = 0; public int CalledApplyBrakes = 0; public bool HonkHorn() { this.CalledHonkHorn++; return true; } public bool ApplyBrakes() { this.CalledApplyBrakes++; return true; }}
注意我們定義的兩個int成員,在我們的單元測試中我們將用他們判斷HonkHorn()和ApplyBrakes()的調用情況。
現在我們可以寫單元測試了,我們將測試兩個行為:
1、Can_Evade_Trouble
2、Can_Evade_Trouble_And_Alert_Offending_Driver
[TestMethod]public void Can_Evade_Trouble(){ // Arrange (set up a scenario) FakeVehicle fakeVehicle = new FakeVehicle(); Driver target = new Driver(fakeVehicle); // Act (attempt the operation) bool success = target.EvasiveManeuvers(false); // Assert (verify the result) Assert.IsTrue(success); Assert.IsTrue(fakeVehicle.CalledHonkHorn == 0); Assert.IsTrue(fakeVehicle.CalledApplyBrakes == 1);}[TestMethod]public void Can_Evade_Trouble_And_Alert_Offending_Driver(){ // Arrange (set up a scenario) FakeVehicle fakeVehicle = new FakeVehicle(); Driver target = new Driver(fakeVehicle); // Act (attempt the operation) bool success = target.EvasiveManeuvers(true); // Assert (verify the result) Assert.IsTrue(success); Assert.IsTrue(fakeVehicle.CalledHonkHorn == 1); Assert.IsTrue(fakeVehicle.CalledApplyBrakes == 1);}
OK,現在我們成功的通過單元測試。他們的EvasiveManeuvers()方法都返回true,並且IVechicle.ApplyBrakes()方法每次都被調用了,HonkHorn()方法第一個測試沒有被調用,第二次調用了。
注意,在真正的測試驅動開發
TDD(Test Driven Development)我們首先要寫測試,然後才是寫代碼再測試,但我們沒這麼做。
這是一個比較不太討人喜歡的寫測試的風格,為了通過Driver我們寫了一個FakeVehicle,假如需要寫很多這種類那就麻煩了。
為瞭解決這種問題,Mocking Framework誕生了。
我們現在用一個叫Moq的架構(https://code.google.com/p/moq/),他的文法獨特,但用過之後你會感到寫起來很流暢。只要把moq.dll添加到引用,然後using Moq就可以了。
現在我們重寫上面的測試代碼,回頭再解釋它的牛逼的地方。
[TestMethod]public void Can_Evade_Trouble(){ // Arrange (set up a scenario) Mock<IVehicle> mock = new Mock<IVehicle>(); mock.Setup(x => x.ApplyBrakes()).Returns(true); Driver target = new Driver(mock.Object); // Act (attempt the operation) bool success = target.EvasiveManeuvers(false); // Assert (verify the result) Assert.IsTrue(success); mock.Verify(x => x.HonkHorn(), Times.Never()); mock.Verify(x => x.ApplyBrakes(), Times.Once());}[TestMethod]public void Can_Evade_Trouble_And_Alert_Offending_Driver(){ // Arrange (set up a scenario) Mock<IVehicle> mock = new Mock<IVehicle>(); mock.Setup(x => x.HonkHorn()).Returns(true); mock.Setup(x => x.ApplyBrakes()).Returns(true); Driver target = new Driver(mock.Object); // Act (attempt the operation) bool success = target.EvasiveManeuvers(true); // Assert (verify the result) Assert.IsTrue(success); mock.Verify(x => x.HonkHorn(), Times.Once()); mock.Verify(x => x.ApplyBrakes(), Times.Once());}
不管你信不信,反正我們可以丟掉FakeVehicle類了。
Moq動態構造了介面的實作類別,所有的成員預設的值都是其預設值。
由於bool類型的預設值是false,所以HonkHorn()和ApplyBrakes()在mock.Object執行個體中都將返回false,顯然我希望返回true的,所以用Moq的Setup()方法來解決。
Setup參數是一個lambda運算式,可以強型別的方式直接存取到其成員。例如
mock.Setup(x=>x.HonkHorn().Returns(true));
如果不用lambda用字串,類似mock.Setup("HonkHorn").Returns(true),這種方式比較醜,如果介面變化了這邊就該報錯了。
moq用lambda就是保證所有的訪問都是強型別的。
另外如果你的方法接受一些參數比如string例如
mock.Setup(x => x.HonkHorn("loudly");
如果這個值不是必須的(不是這個值就不能通過),那就可以用It類代替,他包含很多有用的方法。下面的例子就是接受任一字元串
mock.Setup(x => x.HonkHorn(It.IsAny<string>())).Returns(true);
Moq可以讓你建立任意類型T的Mock<T>執行個體,然後調用Setup()去設定屬性或方法的傳回值,隨便什麼值只要是為你達到測試的目的。加入你需要一個屬性返回一個集合,你只需要定義好集合類,並且通過Setup()的Returns方法返回集合就行了。當這個mock.Object的集合屬性被訪問時就會返回你定義好的集合。
記住:1、mock的對象不是你的測試,你mock的對象是讓你能夠通過他們進入你要測試的類/組件。
另外注意 var mock = new Mock<T>。mock可不是T的執行個體,mock是Mock<T>的對象執行個體,T的執行個體是mock.Object。所以不要搞混了;執行個體化Driver時要用mock.Object。
//Do thisMock<IVehicle> mock = new Mock<IVehicle>();Driver driver = new Driver(mock.Object);//Not thisMock<IVehicle> mock = new Mock<IVehicle>();Driver driver = new Driver(mock);
現在回過頭看看我們的moq的單元測試,所有的結果都是對的。調用次數也是對的,moq能自動記錄方法的調用次數,我們只需要調用mock.Verify()然後通過lambda運算式就能驗證我們想要的次數是否正確。
這裡是最基礎的moq用法,希望你現在能夠明白mockingframework的用處並明白moq怎麼完成工作的。