Mock 入門,分析stub . mock區別


面向接口編程的測試難的問題

Mock Framework的用處在於我們可以在不實現具體對象的情況下,即在沒有某個類的實例的情況下對該對象的行為進行模擬。這一特征對於面向接口的編程非常有用。因為接口的調用者可以在沒有接口的具體實現的情況下使用接口,也就是說調用者可以先於接口的實現者行動。也許有人覺得這好像沒什么神奇的,即使沒有mock我也一樣可以使用接口啊,可是我要問:

  “在沒有接口實現的情況下,你能對調用接口的代碼進行測試嗎?”

“NullReferenceException”相信很多人都碰到過的吧。由於接口不能定義構造函數,也就無法實例化,導致了調用接口的代碼無法運行,當然也就是無法測試。

Mocking能干什么?

從mock 的字面意思就可以了解一二了,它的主要工作是模擬出一個被模擬對象的實例,其中包括模擬對該實例的調用行為(比如訪問屬性、調用方法之類)、模擬方法或屬性訪問的返回值、模擬方法和索引的參數傳遞等等,可以說基本上對於一個對象實例的使用它都可以模擬出來。這樣一來,我們就可以好像真的有一個我們需要的實例存在一樣,正常地使用它,來完成對調用者代碼的開發和測試。

Mock object和stub object一樣嗎?

當然不一樣!寫過stub測試程序的人應該知道,stub是真是對象的一個模擬,比如調用者需要一個值,那就讓stub輸出一個值,如果調用者需要傳遞一個值給stub,那就在stub中定義一個方法接受該參數。但是這與mock的對象存在本質的區別:

stub雖然說也是模擬,但其本質上對真是對象的一個簡單實現,而無論它有多簡單它都是一種實現,它是真是存在的,它里面包含了我們定義的操作代碼;

反觀mock的對象,它根本是不存在的,哪怕一句的簡單的不能再簡單的代碼都不存在。

 

在理解其區別之前,需要明白一點,他們都是為了同一個目標而出現的,代替依賴部分,讓原先的“整合測試”簡化為“單元測試”。       

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>();

只不過是個空殼:)


免責聲明!

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



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