【ASP.Net MVC3 】使用Moq讓單元測試變得更簡單

來源:互聯網
上載者:User

前幾天調查完了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://code.google.com/p/moq/wiki/QuickStarthttp://codeclimber.net.nz/archive/2009/10/23/10-resources-to-learn-moq.aspxhttp://blogs.clariusconsulting.net/kzu/tag/moq/

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

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.