在上節中,完成了第一個單元測試,研究了各類特性,在本節,將介紹一些更實際的例子。SUT依賴於一個不可操控的對象,最多見的例子是文件系統,線程,內存和時間等。html
本系列將分紅3節:web
本節索引:編程
僞對象(fake) 樁對象(stub) 模擬對象(mock)框架
僞對象是一個通用術語,它便可指樁對象,也可指模擬對象。函數
樁對象是指對系統中現有依賴項的一個替代品,可人爲控制。單元測試
模擬對象是用來決定一個單元測試是經過仍是失敗的僞對象。測試
說明:fake是stub和mock的統稱,由於看起來都像是真的對象。若是是用來檢查交互的就是模擬對象,不然就是樁對象this
樁對象:.net
模擬對象:線程
如何處理?
本質上都是外部依賴致使的,因此要作的是消除依賴。
注入樁對象
隱藏樁對象(因爲生產環境等其餘緣由,咱們不但願暴露樁對象)
使用樁對象(適用於模擬返回值,不適用於檢查對象間的交互狀況。)
這是很是常見的方式,可是這種方式受限制不少,如文件須要配置,運行慢。
public class Config { public bool IsCheck(string name) { var str = File.ReadAllText("1.txt"); return str == name;//此處多是大量的邏輯處理 } }
改寫注入
public class Config { private IManager manager; //提供注入接口 public Config(IManager manager) { this.manager = manager; } public bool IsCheck(string name) { var str = manager.GetConfig(); return str == name; } } //真實的實現 public class FileManager : IManager { public string GetConfig() { return File.ReadAllText("1.txt"); } } //測試使用的實現 public class StubManager : IManager { public string GetConfig() { return "str"; } } //抽象出的接口 public interface IManager { string GetConfig(); }
測試代碼
[TestClass] public class ConfigTests { private Config config; [TestInitialize] public void Init() { config = new Config(new StubManager()); } [TestMethod] public void IsCheckTest() { Assert.IsTrue(config.IsCheck("str")); } [TestCleanup] public void Clean() { config = null; } }
使用模擬對象(適用於對象之間的交互)
當上面的方法返回false的時候,須要調用別的web服務記錄下。而web服務還未開發好,即便開發好了,測試的時間也會變長不少。
這裏其實也體現了,stub的優勢,能夠任意的控制返回結果。
新建一個mock
public class Config { private IManager manager; public IWeb Web { get; set; } public Config(IManager manager) { this.manager = manager; } public bool IsCheck(string name) { var str = manager.GetConfig(); var rst = str == name; if (!rst) Web.Log("錯誤"); return rst; } } /// <summary> /// 模擬對象 /// </summary> public class MockWeb : IWeb { public string Erro { get; set; } public void Log(string erro) { Erro = erro; } } public interface IWeb { void Log(string erro); }
測試代碼
[TestClass] public class WebTests { [TestMethod] public void LogTest() { var web = new MockWeb(); //注入的方式很是多 var config = new Config(new StubManager()) { Web = web }; config.IsCheck("s"); //最終斷言的是模擬對象。 Assert.AreEqual("錯誤", web.Erro); } }
注意:一個測試只有一個mock,其餘僞對象都是stub,若是存在多個mock,說明這個單元測試是在測多個事情,這樣會讓測試變得複雜和脆弱。
隔離框架簡介
手寫stub和mock很是麻煩耗時,並且不易看懂等缺點。
隔離框架是能夠方便的新建stub和mock的一組可編程API。
.net下常見的有Rhino Mocks,Moq
這裏使用RhinoMocks作示例(將使用錄製回放模式和操做斷言2種)
錄製回放
新建mock對象
來實現一個和上面mock的例子
[TestMethod] public void LogMockTest() { var mocks = new MockRepository();
//嚴格模擬對象 var mockWeb = mocks.StrictMock<IWeb>(); using (mocks.Record())//錄製預期行爲 { mockWeb.Log("錯誤"); } var config = new Config(new StubManager()) { Web = mockWeb }; config.IsCheck("s"); mocks.Verify(mockWeb); }
嚴格模擬對象:是指只要出現預期行爲之外的狀況,就報錯。
非嚴格模擬對象:是指執行到最後一行,纔會報錯。
新建stub對象
[TestMethod] public void LogStubTest() { var mocks = new MockRepository(); //非嚴格對象 var mockWeb = mocks.DynamicMock<IWeb>(); //樁對象 var stubManager = mocks.Stub<IManager>(); using (mocks.Record()) { mockWeb.Log("錯誤1"); stubManager.GetConfig(); LastCall.Return("str1"); //錄製樁對象返回值 } var config = new Config(stubManager) { Web = mockWeb }; config.IsCheck("str"); mocks.Verify(stubManager); //樁對象不會致使測試失敗 mocks.VerifyAll(); //啓用非嚴格對象,測試直到這裏纔會確認是否報錯 }
操做斷言
[TestMethod] public void LogReplayTest() { var mocks = new MockRepository(); var mockWeb = mocks.DynamicMock<IWeb>(); var config = new Config(new StubManager()) { Web = mockWeb }; //開始操做模式 mocks.ReplayAll(); config.IsCheck("str1"); //使用Rhino Mocks斷言 mockWeb.AssertWasCalled(o => o.Log("錯誤")); }
注意:使用框架建立的動態僞對象,確定沒手工編寫的僞對象執行效率高。
本文做者:Never、C
本文連接:http://www.cnblogs.com/neverc/p/4749197.html