如何使用MOQ進行單元測試

lvxfcjf發表於2021-09-09

使用MOQ來偽裝和隔離被依賴物件,從而提高被測物件的測試效果。

 

安裝

透過可以下載MOQ的最新版本。在SSL專案中,我們使用的是MOQ 3.1.416.3版本。在SCM中專案目錄下的Lib目錄下有該工具的二進位制版本。直接在單元測試專案中引用即可。

 

準備工作

如果你需要測試專案中的Internal成員,你需要在AssemblyInfo.cs中新增如下的Attribute:

?

#if DEBUG[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2,PublicKey=00240000048000009400000006020000002400005253" +"41310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266" +"654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c" +"4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]#endif

 

開始測試

一個單元測試的一般流程:

 

一般情況下,一個單元測試應該被分割為如下四個步驟:

  • 準備

    • 搭建環境

    • 構造被測物件

    • 初始化被測物件

    • 構造Mock物件

    • 初始化Mock物件

    • 連線被測物件和依賴項

  • 宣告期待

    • 配置Mock(Mock.Setup)物件以宣告該Mock物件期待被怎樣呼叫。

  • 執行測試

    • 呼叫被測物件的方法,完成測試步驟

  • 校驗測試結果

    • 呼叫校驗方法(Mock.VerifyAll)對Mock物件上的期待動作進行校驗。

    • 使用Assert方法對被測物件的狀態進行校驗。

 

一個單元測試的例子:

?

[TestMethod]public void TestNavigationSyncWithSelection(){    // 測試如果修改Selection,那麼NavigationService.MoveCurrentTo方法應該被呼叫。    // 1. 準備    // 1.1 搭建環境    var c = new ServiceContainer();    var dataManager = new SpreadSheetDataManager(32, 8);    // 1.2 構造被測物件    var selectionService = new SelectionService();    // 1.3 初始化被測物件     // 1.4 構造Mock物件    var mockNavigationService = new Mock();    // 1.5 初始化和配置Mock物件    c.AddService(mockNavigationService);    mockNavigationService.Setup(s => s.CanMoveCurrentTo(It.IsAny()))        .Returns(true);     // 1.6 連線被測物件和依賴項    c.AddService(selectionService); // 這裡隱式的將SelectionService和NavigationService連線在一起了。因為他們都被放到了一個容器裡面。    (selectionService as IService).Attach(c, dataManager);     // 2. 宣告期待    mockNavigationService.Setup(s => s.MoveCurrentTo(new CellPosition(0, 1))); // navigationService的MoveCurrentTo方法期待被呼叫,並且引數為【0,1】。     // 3. 執行測試    selectionService.Select(new CellRange(0, 1, 2, 2));     // 4. 校驗    // 4.1 校驗Mock物件期待的動作被正確的呼叫了。    mockNavigationService.VerifyAll();    // 4.2 校驗被測物件的狀態。    Assert.AreEqual(new CellRange(0, 1, 2, 2), selectionService.CurrentSelection);}

 

推薦的單元測試寫法

目前的單元測試中,往往準備工作很複雜,反而真正測試的工作比較簡單。就像上面的例子中,準備的程式碼寫了8行,其它真正測試所關心的程式碼卻只有4行。這是一個非常不舒服的狀態。但是,我也沒有找到更好的方式來解決這個問題。只能說在架構上讓各個模組的依賴儘可能的小,從而減少準備工作的量。

 

另一方面,透過在程式碼中適當的增加幾行註釋,可以很好的幫助閱讀的人找到重點。我覺的如果整個團隊都採用一致的編碼習慣,閱讀效率會提高很多。如下是上面的例子去除了多餘的註釋後的版本。

 

?

[TestMethod]public void TestNavigationSyncWithSelection(){    // 測試如果修改Selection,那麼NavigationService.MoveCurrentTo方法應該被呼叫。       // Prepare    var c = new ServiceContainer();    var dataManager = new SpreadSheetDataManager(32, 8);     var selectionService = new SelectionService();     var mockNavigationService = new Mock();    c.AddService(mockNavigationService);    mockNavigationService.Setup(s => s.CanMoveCurrentTo(It.IsAny()))        .Returns(true);     c.AddService(selectionService);    (selectionService as IService).Attach(c, dataManager);     // Expect    mockNavigationService.Setup(s => s.MoveCurrentTo(new CellPosition(0, 1)));     // Act    selectionService.Select(new CellRange(0, 1, 2, 2));     // Verity    mockNavigationService.VerifyAll();    Assert.AreEqual(new CellRange(0, 1, 2, 2), selectionService.CurrentSelection);}

 

 

擴充套件閱讀:Mocks Aren't Stubs

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/3486/viewspace-2806261/,如需轉載,請註明出處,否則將追究法律責任。

相關文章