.NET單元測試(三):隔離框架

  上一篇內容中咱們講到僞對象,並寫了一個僞對象,若是閱讀文章的你剛剛手寫完僞對象,那麼我得告訴你,之後你都不須要再手寫僞對象了(不得不說,手寫僞對象至少可以加深對隔離框架的理解)。html

何爲隔離框架?

  一個可以在運行時新建和配置僞對象的可重用的類庫,它讓開發者不用爲了僞對象而編寫重複的代碼。bash

即:隔離框架能夠替咱們動態的生成須要的僞對象,節省不少精力。框架

選擇隔離框架

  目前市面上有不少隔離框架,能夠根據團隊狀況選擇,這裏推薦Moq,主要基於如下緣由:函數

  1. 強類型:不支持使用字符串來設置指望
  2. 再也不須要學習錄製/播放,只須要構建你本身的Mock,設置好你的指望,調用它,而後有選擇地驗證它們便可
  3. 不用去學習Mock、Stub之間的理論差別了
  4. 能夠對接口和類進行Mock
  5. 重載指望:能夠在全局設置時給Mock方法設置缺省的指望,在測試方法中能夠根據須要對它進行重載。
  6. 它的學習曲線極低,大多數狀況下,你甚至無須閱讀文檔。
  7. 免費開源
  8. ...

(若是以前沒有了解過隔離框架,以上二、3點可暫時不予理會)單元測試

Moq使用

  先來了解一下使用Moq的通常套路,定義如下接口:學習

public interface ITextReader
{
    void BeginRead();
 
    string Read();
 
    void EndRead();
}
複製代碼

定義一個ITextReader的業務方:測試

public static bool IsValidHtml(ITextReader textReader)
{
    var htmlString = textReader.Read();
    return htmlString.Contains("html");
}
複製代碼

IsValidHtml方法依賴ITextReader接口來讀取字符串,並判斷字符串是不是合法html。ui

使用Moq來隔離ITextReader會多簡單呢?spa

[TestMethod]
public void IsValidHtml_EmptyString_returnFalse()
{
    //Arrange
    //新建一個ITextReader的Mock對象,其Object屬性即爲咱們須要的僞對象
    var textReaderMock = new Mock<ITextReader>();
 
 
    //對僞對象的方法進行mock
    //當調用ITextReader接口的Read()方法時,將返回Empty字符串
    textReaderMock.Setup(x => x.Read()).Returns(string.Empty);
 
    //Action
    //將僞對象注入到被測試方法中
    var result = Document.IsValidHtml(textReaderMock.Object);
 
    //Assert
    Assert.IsFalse(result);
}
複製代碼

簡單吧(●'◡'●)code

接下來咱們瞭解一下Moq提供的API

  • Setup+Return 請參見上面的例子

  • Setup+Callback 當指定方法被調用時,能夠收到一個回調函數,方法調用的參數將做爲回調函數的參數傳遞。請看例子:

//被依賴的第三方接口
    public interface IPaint
    {
        void AddElement(int element);
    
        bool CouldBeSelected();
    
        event Action<int, int> SelectionChanged;
    }
    


    //IPaint接口的業務方
    private readonly IPaint _paint;
    public Document(IPaint paint)
    {
        _paint = paint;
    }

    public void AddElements(IEnumerable<int> elements)
    {
        foreach (var i in elements.ToList())
        {
            _paint.AddElement(i);
        }
    }


    //AddElements的單元測試方法
    [TestMethod]
    public void AddElements_MultiElements_ShouldCallAddElementsMultiTimes()
    {
        var paintMock = new Mock<IPaint>();

        var input = new List<int>()
        {
            0,1,2,3,4
        };

        var expected = new List<int>();
        //當調用IPaint接口的AddElement方法,且參數是任意int時,出發回掉函數
        paintMock.Setup(x => x.AddElement(It.IsAny<int>())).Callback<int>(i =>
        {
            expected.Add(i);
        });

        var document = new Document(paintMock.Object);

        document.AddElements(input);

        CollectionAssert.AreEqual(input, expected);
    }
複製代碼
  • SetupSequence 設置對方法對連續調用的響應:
[TestMethod]
 public void GetPaintCouldBeSelected_CallTwoTimes_ReturnTrueAndFalse()
 {
     var paintMock = new Mock<IPaint>();
     //連續調用CouldBeSelected方法時,第一次返回true,第二次返回false
     paintMock.SetupSequence(x => x.CouldBeSelected()).Returns(true).Returns(false);
 
     var document = new Document(paintMock.Object);
 
     var result = document.GetPaintCouldBeSelected();
     Assert.IsTrue(result);
     result = document.GetPaintCouldBeSelected();
     Assert.IsFalse(result);
 }
複製代碼
  • Verify+Times 對一個方法調用次數進行驗證:
[TestMethod]
public void AddElements_SingleElement_ShouldCallAddElement()
{
    var paintMock = new Mock<IPaint>();

    //將以任意int做爲參數的AddElement方法的調用進行標記,在調用paintMock.Verify方法時對AddElement方法是否通過調用進行驗證
    paintMock.Setup(x => x.AddElement(It.IsAny<int>())).Verifiable();

    //----------
    //作了一些事情,好比調用了IPaint的AddElement方法
    //----------
    var document = new Document(paintMock.Object);
    document.AddElements(new List<int>() { 1, 2, 3 });

    //驗證AddElement是否通過調用
    paintMock.Verify();
}
複製代碼

當須要對調用次數作限制時,也可使用另外一種方式:

[TestMethod]
public void AddElements_SingleElement_ShouldCallAddElement()
{
    var paintMock = new Mock<IPaint>();

    paintMock.Verify(x => x.AddElement(It.IsAny<int>()), Times.Between(1, 3, Range.Inclusive));

    //----------
    //作了一些事情,好比調用了IPaint的AddElement方法
    //----------
    var document = new Document(paintMock.Object);
    document.AddElements(new List<int>() { 1, 2, 3 });

    //驗證AddElement的調用次數是否爲1-3
    Mock.Verify(paintMock);
}
複製代碼
  • It It用於對參數進行限制,能夠指定參數必須是什麼類型,必須知足特定的正則,必須處於某個範圍等,具體可參見API。

至此,Moq的基本API就完結了,是否很簡單呢?

除了上面的部分,Moq還提供了一些功能,可是使用方法都跟上面的相似

  • Setup+Callbase(調用基類的方法)

  • SetupGet(獲取屬性)

  • SetupSet(設置屬性)

  • ...

Moq的限制

  Moq的機制是對於Mock的接口,生成一個實現類,這個實現類裏的方法都沒有具體的實現,而是根據用戶的設置直接返回。

由此咱們能夠得出Moq的限制:

  • 必須是能夠被繼承的對象才能被mock
  • 必須是能夠被重寫的方法才能被mock
2017-3-18 23:36:16
相關文章
相關標籤/搜索