Mock Framework的用處在於咱們能夠在不實現具體對象的狀況下,即在沒有某個類的實例的狀況下對該對象的行爲進行模擬。這一特徵對於面向接口的編程很是有用。由於接口的調用者能夠在沒有接口的具體實現的狀況下使用接口,也就是說調用者能夠先於接口的實現者行動。也許有人以爲這好像沒什麼神奇的,即便沒有mock我也同樣可使用接口啊,但是我要問:編程
「在沒有接口實現的狀況下,你能對調用接口的代碼進行測試嗎?」框架
「NullReferenceException」相信不少人都碰到過的吧。因爲接口不能定義構造函數,也就沒法實例化,致使了調用接口的代碼沒法運行,固然也就是沒法測試。函數
從mock 的字面意思就能夠了解一二了,它的主要工做是模擬出一個被模擬對象的實例,其中包括模擬對該實例的調用行爲(好比訪問屬性、調用方法之類)、模擬方法或屬性訪問的返回值、模擬方法和索引的參數傳遞等等,能夠說基本上對於一個對象實例的使用它均可以模擬出來。這樣一來,咱們就能夠好像真的有一個咱們須要的實例存在同樣,正常地使用它,來完成對調用者代碼的開發和測試。單元測試
固然不同!寫過stub測試程序的人應該知道,stub是真是對象的一個模擬,好比調用者須要一個值,那就讓stub輸出一個值,若是調用者須要傳遞一個值給stub,那就在stub中定義一個方法接受該參數。可是這與mock的對象存在本質的區別:測試
stub雖說也是模擬,但其本質上對真是對象的一個簡單實現,而不管它有多簡單它都是一種實現,它是真是存在的,它裏面包含了咱們定義的操做代碼;網站
反觀mock的對象,它根本是不存在的,哪怕一句的簡單的不能再簡單的代碼都不存在。spa
在理解其區別以前,須要明白一點,他們都是爲了同一個目標而出現的,代替依賴部分,讓原先的「整合測試」簡化爲「單元測試」。 3d
mock:使用easymock等包,在程序代碼中向被測試代碼注入「依賴部分」,經過代碼可編程的方式模擬出函數調用返回的結果。對象
stub:本身寫代碼代替「依賴部分」。它自己就是「依賴部分」的一個簡化實現。索引
實際上,在可以使用mock的時候,就不該該選擇使用stub。可是有時候是必須使用stub的,例如在對遺留代碼進行測試時,該部分代碼不支持「注入」,那麼只能將「替代」這個過程外移,使用stub完成此任務了。
就以我如今正在開發這個網站代碼爲例,來講一下若是在測試的使用Mock object.如今有一個需求,咱們須要根據給定的搜索關鍵字和搜索範圍來進行項目的搜索,以MVP的方式實現的話咱們定義了一個IView接口:
public interface IView_SearchProject
{
void AttachPresenter(Presenter_SearchProject presenterSearchProject);
SearchRange Range { get;}
string SearchKey { get;}
string UrlBase { get;}
void NavigateTo(string searchUrl);
}
以及一個Presenter:
public class Presenter_SearchProject
{
public Presenter_SearchProject(IView_SearchProject viewSearch)
{
view = viewSearch;
range = view.Range;
prjNav = new ProjectSearchNavigator(view.UrlBase);
query = new SearchQuery();
}
public string GetDesUrl()
{
query.WithDescription = range.WithDescription;
query.WithName = range.WithName;
query.WithKey = range.WithKey;
query.SearchKey = view.SearchKey;
query.Ids = range.Ids;
prjNav.Compile(query);
return prjNav.DestUrl;
}
public void Search()
{
view.NavigateTo(GetDesUrl());
}
private IView_SearchProject view;
private SearchRange range;
private ProjectSearchNavigator prjNav;
private SearchQuery query;
}
ProjectSearchNavigator是一個實現頁面跳轉的幫助類,負責根據View(這裏是一個aspx的頁面)傳遞的搜索關鍵字SearchKey和querystring構造出搜索頁面的地址。SearchQuery類負責解析Request.QueryString集合,由於其中存儲的key/value對,須要據此構造出全部查詢條件的一個字符串。
Mocking and Testing
Mocking說到底多試爲了測試,不然咱們沒有必要,由於mocking出來的對象並不能做爲的真是的代碼運行。先把測試的代碼貼出來,再進行解釋,但願你不要以爲太多了:)
[TestFixture]
public class Presenter_SearchProject_Test
{
[SetUp]
public void SetUp()
{
mockRepository=new MockRepository();//1
mockView = mockRepository.CreateMock<IView_SearchProject>();//2
}
[Test]
public void GetDestUrl()
{
SearchRange range = new SearchRange(true, true, false, string.Empty);
//3
//
Expect.Call(mockView.Range).Return(range) ;
//UrlBase
Expect.Call(mockView.UrlBase).Return("http://localhost");
//SearchKey
Expect.Call(mockView.SearchKey).Return("searchKey");
//4
mockRepository.ReplayAll();
//5
presenter = new Presenter_SearchProject(mockView);
string destUrlReturned = presenter.GetDesUrl();
string destUrlExpected = "http://localhost/ProjectPage/ProjectControl.aspx?"
+"search=searchKey&name=True&key=True&description=False";
//6
Assert.AreEqual(destUrlExpected,destUrlReturned);
}
IView_SearchProject mockView;
MockRepository mockRepository;
Presenter_SearchProject presenter;
[TearDown]
public void TestCleanup()
{
mockRepository.ReplayAll();
mockRepository.VerifyAll();
}
}
1. Rhion.Mock框架中要使用mock的對象都須要從MockRepository 這個對象中產生,它充當一個對象工廠的角色。
2. 這一步就是建立咱們使用的mock的對象了,須要以被mock類的類型做爲泛型參數。
3. 這一步的3行代碼是真正mock的部分,它們分別對應着對mockView的三次調用。
Expect.Call(mockView.Range).Return(range) ;
Expect.Call表示咱們但願調用mockVIew的那個方法,也包括屬性。
.Return的意思咱們打算讓這個模擬對象返回什麼樣的值
綜合起來的意思就是:咱們但願mockView的調用者在調用MockView的某一個方法(或屬性)時返回一個有return標識的值
4. ReplayAll的調用千萬不要忘掉,它的意思能夠理解爲,讓以前設定的模擬行爲生效,今後以後咱們就能夠把這個mock的對象看成是一個真是的對象來使用了。我以爲能夠把它想像成CLR爲咱們自動生成了代碼同樣,爲咱們生成了一個對被mock對象的實現。
5. 這一步是調用者對mock對象的使用。
6. 測試咱們關注的對象的行爲是否正常
presenter = new Presenter_SearchProject(mockView);
像這樣的初始化須要注意順序,必需要等到MockView被真正模擬出來以後,也就是ReplayAll調用以後,由於在presenter 內部須要訪問mockView的成員,好比:
range = view.Range;
可是若是你在mock對象調用者初始化的時候沒有訪問mock對象的成員,那麼這樣的初始化能夠的。由於雖然mock對象的成員還米有mock出來,可是mock對象已經被生成了:
mockView = mockRepository.CreateMock<IView_SearchProject>();
只不過是個空殼:)