[Test] 單元測試藝術(2) 打破依賴,使用模擬對象,樁對象,隔離框架

在上節中,完成了第一個單元測試,研究了各類特性,在本節,將介紹一些更實際的例子。SUT依賴於一個不可操控的對象,最多見的例子是文件系統,線程,內存和時間等。html

 

本系列將分紅3節:web

  1. 單元測試基礎知識
  2. 打破依賴,使用模擬對象,樁對象,隔離框架
  3. 建立優秀的單元測試

 

本節索引:編程

 

 

僞對象(fake) 樁對象(stub) 模擬對象(mock)框架

僞對象是一個通用術語,它便可指樁對象,也可指模擬對象。函數

樁對象是指對系統中現有依賴項的一個替代品,可人爲控制。單元測試

模擬對象是用來決定一個單元測試是經過仍是失敗的僞對象。測試

 

說明:fake是stub和mock的統稱,由於看起來都像是真的對象。若是是用來檢查交互的就是模擬對象,不然就是樁對象this

 

樁對象:.net

 

模擬對象:線程

 

爲何須要僞對象

  1. 外部依賴(系統中代碼與其交互的對象,並且沒法對其作人爲控制)
  2. 反測試(而一旦測試中存在外部依賴,那麼這個測試就是一個集成測試。運行慢,須要配置,依賴異常)

 

如何處理?

本質上都是外部依賴致使的,因此要作的是消除依賴。

  1. 分析接口
  2. 實現可人爲控制的接口 

 

注入樁對象

  1. 在構造函數上接受一個接口,並保存在一個字段裏,以備後用。
  2. 保存在屬性上
  3. 在調用方法前,使用方法參數,工廠類,依賴注入等

 

隱藏樁對象(因爲生產環境等其餘緣由,咱們不但願暴露樁對象)

  1. 使用條件編譯
  2. 使用條件特性
  3. 使用internal和[InternalVisibleTo]

 

 

手工新建僞對象

 

使用樁對象(適用於模擬返回值,不適用於檢查對象間的交互狀況。)

 

這是很是常見的方式,可是這種方式受限制不少,如文件須要配置,運行慢。

  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

相關文章
相關標籤/搜索