用Moq讓單元測試變得更簡單


前幾天調查完了unity。現在給我的任務是讓我調查Moq。

以下是自己找了資料,總結並實踐的內容。如果有表述和理解錯誤的地方。懇請指正。

什么是Moq?

 

Moq(英語發音是Mock-you 或者只是mock)是一個針對.Net開發的模擬庫,它從開始就完全充分利用了.NET3.5(LINQ表達式樹)和C#3.0的新特性(lambda表 達式)。它的目標是讓模擬以一種自然的方式與現有單元測試進行集成,使它更加簡單、直觀,以避免開發人員被迫重寫測試或高成本的學習測試框架。這使它成為 了一個高生產力、類型安全、重構友好的模擬庫。

從哪得到Moq?

 

如果你看過我的其他文章,我們可以直接使用 VS中的插件Nuget來獲取Moq並且引用到指定的項目。

否則,我們可以從http://code.google.com/p/moq/這里得到Moq的最新版本。

可以模擬什么?局限性

 

首先,模擬的類不能是密封的。

其次,你不能直接模擬靜態方法。因為Moq只能創建模擬對象實例。在這種情況下,間接的解決方案是我們可以在要模擬對象外包裝一層,並且去模擬這個新對象。這種模式被稱為適配器模式。

通常我們測試一個方法,它有可能調用好幾個service。但是每次都去訪問這些service的代價是很高的。我們可以通過模擬的方法讓它模擬訪問service,並且根據不同請求模擬返回響應的結果。

Moq原理

 

Moq是如何辦到的?它只需要一個接口類型就可以生產一個對象?沒錯,就是這樣。Moq使用 Castle DynamicProxy 完成這個任務。基本原理就是它利用反射機制的 Emit 功能動態生成一個空類型(也就是所有接口的方法都實例化,但是沒有任何功能,只是一個程序骨架)。所以Mock的能力就在於可以利用DynamicProxy的機制快速生產出一個假對象來,用於模仿真對象的行為。

 

Moq中的重要成員

 

Mock

通過這個類,我們可以得到一個Mock<T>對象。T可以是接口,也可以是類。它有一個public 和virtual屬性。讓我們看看下邊的例子:

復制代碼
        //define interface to be mocked

public interface IFake
{

bool DoSomething(string actionname);

}

//define the test method

[TestMethod]

public void Test_Interface_IFake()
{

//make a mock Object by Moq

var mo = new Mock<IFake>();

//Setup our mock object

mo.Setup(foo => foo.DoSomething("Ping"))

.Returns(true);

//Assert it!

Assert.AreEqual(true, mo.Object.DoSomething("Ping"));
}
復制代碼

在上邊的代碼,我們通過傳遞泛型參數IFake去創建Mock<IFake>的實例 模擬接口IFake。

接下來我們要調用Setup()方法去創建我們的模擬對象。注意,Setup方法的參數是一個lambda表達式。我們可以這樣理解:當被模擬的對 象foo調用它自己的方法DoSomething(),並且參數是Ping。添加后綴 Return (true)我們可以理解為:前邊的請求返回結果為真。這是我們指定的返回值。當一個請求調用DoSomething()方法時。如果傳入的參數是 Ping,那么我們會返回true。接下來,我們添加一個斷言,去判斷是否能得到預期結果。

注:Foo僅僅是一個詞用作通用替代真實的東西,特別是在討論技術想法和問題.

It

 

這是一個靜態類,定義了靜態的泛型方法:Is<TValue>, IsAny<TValue>, IsInRange<TValue>, 和IsRegex。去過濾參數。看看下邊例子:

復制代碼
public interface IEmailSender
{

bool Send(string subject, string body, string email);

}
[TestMethod]

public void User_Can_Send_Password()
{

var emailMock = new Mock<IEmailSender>();

emailMock

.Setup(sender => sender.Send(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()))

.Returns(true);

}
復制代碼

任何時候調用Send()方法,只要傳入的參數是任何的string,我們定義他會返回true。

我們也可以根據lambda的優勢訂制一個規則:

          

復制代碼
  var productRepository = new Mock<IProductRepository>();

productRepository

.Expect(p => p.Get(It.Is<int>(id => id > 0 && id < 6)))

.Returns(newProduct.Object);
復制代碼

 

這樣我們可以設置這個id在0和6之間的時候才會返回一個新的對象。上邊提及到的其他方法,我們可以參考這里的教程 Moq’s QuickStart

此外,這個類的增強版是 Match<T>,你完全可以自定義模擬規則。

MockBehavior

 

這個類用於模擬對象的行為。就像是否按照默認的模式去模擬。讓我們進一步看看他的定義:

復制代碼
namespace Moq
{
// Summary:
// Options to customize the behavior of the mock.
public enum MockBehavior
{
// Summary:
// Causes the mock to always throw an exception for invocations that don't have
// a corresponding setup.
Strict = 0,
//
// Summary:
// Will never throw exceptions, returning default values when necessary (null
// for reference types, zero for value types or empty enumerables and arrays).
Loose = 1,
//
// Summary:
// Default mock behavior, which equals Moq.MockBehavior.Loose.
Default = 1,
}
}
復制代碼

 

現在,看看如下例子:

 

var mock = new Mock<IFake>(MockBehavior.Strict);

指定了mock行為是精准的,如果沒有按照預期的Setup就會拋出異常。

MockFactory

 

這是一個模擬對象的工廠,我們不僅僅可以定制創建模擬對象的配置,也可以成批測試它們。看看下邊例子:

 

復制代碼
var factory = new MockFactory(MockBehavior.Strict) { DefaultValue = DefaultValue.Mock };

// Create a mock using the factory settings

var fooMock = factory.Create<IFake>();

// Create a mock overriding the factory settings

var barMock = factory.Create<IEmailSender>(MockBehavior.Loose);

// Verify all verifiable expectations on all mocks created through the factory

factory.Verify();
復制代碼

在前邊,我們已經介紹了傳統方式的創建模擬對象,它不會去真正的調用方法,而是僅僅去執行一些假設:如果...那么返回... 。

在下邊的例子里,我將繼續介紹一些Mock<T> 類中基本並且重要的方法。

Verification

 

有時候,我們要確定一個方法是否被調用了,或者甚至要知道它被調用了多少次。一個比較傳統的方式是使用Verify()方法。看看下邊例子:

mock.Verify(foo => foo.DoSomething("Ping"), Times.Once());

上邊的代碼嘗試驗證DoSomething("Ping")需要被調用,並且只調用一次。出了Once選項,這里也有更多的選項可供你選擇去決定這 個方法需要被調用多少次。 如: AtLeast, AtLeastOnce, AtMost, AtMostOnce, Between, Equals, Exactly, Never, 和Once

一旦我們已經模擬了對象,驗證將是個輕松的任務,看看下邊的例子:

復制代碼
   [TestMethod]

public void Test_FindByName_GetCalled()
{

// create some mock data

IList<Product> products = new List<Product>

{

new Product { ProductId = 1, Name = "C# Unleashed",

Description = "Short description here", Price = 49.99 },

new Product { ProductId = 2, Name = "ASP.Net Unleashed",

Description = "Short description here", Price = 59.99 },

new Product { ProductId = 3, Name = "Silverlight Unleashed",

Description = "Short description here", Price = 29.99 }

};

Mock<IProductRepository> mock = new Mock<IProductRepository>();

//mock

//.Setup(sender => sender.FindById(It.IsAny<int>()))

//.Returns((int s) => products.Where(

// x => x.ProductId == s).Single());

mock.Object.FindById(1);

mock

.Verify(x => x.FindById(1), Times.Once());

}
}
復制代碼

在上邊的例子里,有兩個地方值得注意。

首先是mock.Object.FindById(1)。為了在這個case里讓一切變得簡單,我們直接調用mock.Object的方法。為什么呢?因為我們這個case只關注這個方法被調用的次數,而不關注返回值。當然,在實際的應用中我們很少這樣做。

第二,你有沒有注意到被注釋掉的句子。它僅僅是一個“如果,那么”句子。意思是說,如果Setup 好了,就返回我們定義的值。

由於調查的時間有限,還有很多關於Mock給力的地方我沒有涉及到。我們可以去查看官方的資料。歡迎共同討論。

 

 

參考資料

http://stephenwalther.com/blog/archive/2008/06/12/tdd-introduction-to-moq.aspx

http://blog.miniasp.com/post/2010/09/16/ASPNET-MVC-Unit-Testing-Part-03-Using-Mock-moq.aspx
http://dotnetslackers.com/articles/aspnet/Built-in-Unit-Test-for-ASP-NET-MVC-3-in-Visual-Studio-2010-Part-2.aspx#s4-using-moq-framework


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM