上一篇內容中咱們講到僞對象,並寫了一個僞對象,若是閱讀文章的你剛剛手寫完僞對象,那麼我得告訴你,之後你都不須要再手寫僞對象了(不得不說,手寫僞對象至少可以加深對隔離框架的理解)。html
一個可以在運行時新建和配置僞對象的可重用的類庫,它讓開發者不用爲了僞對象而編寫重複的代碼。bash
即:隔離框架能夠替咱們動態的生成須要的僞對象,節省不少精力。框架
目前市面上有不少隔離框架,能夠根據團隊狀況選擇,這裏推薦Moq,主要基於如下緣由:函數
(若是以前沒有了解過隔離框架,以上二、3點可暫時不予理會)單元測試
先來了解一下使用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);
}
複製代碼
[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);
}
複製代碼
[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);
}
複製代碼
至此,Moq的基本API就完結了,是否很簡單呢?
除了上面的部分,Moq還提供了一些功能,可是使用方法都跟上面的相似
Setup+Callbase(調用基類的方法)
SetupGet(獲取屬性)
SetupSet(設置屬性)
...
Moq的機制是對於Mock的接口,生成一個實現類,這個實現類裏的方法都沒有具體的實現,而是根據用戶的設置直接返回。
由此咱們能夠得出Moq的限制: