使用 Moq 測試.NET Core 應用 - Why Moq?

solenovex發表於2018-07-12

 什麼是Mock

當對程式碼進行測試的時候, 我們經常需要用到一些模擬(mock)技術.

綠色的是需要被測試的類, 黃色是它的依賴項, 灰色的無關的類

 

在一個專案裡, 我們經常需要把某一部分程式獨立出來以便我們可以對這部分進行測試. 這就要求我們不要考慮專案其餘部分的複雜性, 我們只想關注需要被測試的那部分. 這裡就需要用到模擬(Mock)技術.

因為, 請仔細看. 我們想要隔離測試的這部分程式碼對外部有一個或者多個依賴. 所以編寫測試程式碼的時候, 我們需要提供這些依賴. 而針對隔離測試, 並不應該使用生產時用的依賴項, 所以我們使用模擬版本的依賴項, 這些模擬版依賴項只能用於測試時, 它們會使隔離更加容易.

綠色的是需要被測試的類, 黃色Mock的依賴項

 

Mock技術帶來的優點

使用Mock技術, 可以有如下的優點:

  • 提高測試執行速度, 例如可以模擬DB, Web Service等比較慢的服務, 以及演算法等.
  • 支援並行開發, 例如實際的依賴項還沒有完成開發, 或者等待其他團隊開發依賴項.
  • 提高測試可靠性, 例如有時這個依賴項的bug太多了, 經常由於依賴項的原因導致測試失敗, 那麼就應該使用mock版本來驗證我們自己寫的程式碼.
  • 減少開發/測試成本, 有時程式可能依賴一些雲服務, 這些服務是按呼叫次數收費的, 那麼就可以使用Mock版本來節省這方面的開資, 當然了最後還是需要使用真正的服務測試才行; 有時候組建依賴項太費勁了, 就用mock版本吧, 省時省力.
  • 在有不確定性依賴項的情況下進行測試, 有些依賴項有不確定性, 可能無理由的造成測試失敗, 這時候就應該使用mock版本的依賴.

 

單元測試

Mock技術通常在單元測試中使用, 可以使用xUnit來為.NET Core應用做單元測試, 這裡有介紹xUnit的文章: https://www.cnblogs.com/cgzl/p/9178672.html#xunit

那麼什麼是一個單元? 

這個通常是由團隊對系統的理解決定, 可以針對一個類, 也可以針對多個類.

單元測試通常具有以下特點:

  • 低階別
  • 高聚焦
  • 執行速度快
  • 容易測試所有執行路徑上的程式碼

 

術語

  • Test Double (我認為可以翻譯為測試替身), 是所有非真實依賴項的總稱.
    • Fake, Fake是那種可以正常工作的實現, 儘管可以正常工作, 但是它們不可以用於生產環境, 例如EFCore裡的記憶體資料庫提供商.
    • Dummy, 有時候, 被測試方法需要一些引數, 但是這些引數實際上並沒有用到, 這時就可以建立dummy, 它們的存在只是為了滿足呼叫方法的引數要求.
    • Stub, (狀態測試). 它可以使用很直接的方式模擬依賴項的行為. 例如我們可以使用Stub把相關資料放到記憶體裡查詢而不是查詢真實的資料庫; 如果某個測試類需要依賴項的某個Property的值, 那麼stub就設定這個值就行.
    • Mock, (行為/互動測試). 與Stub不同的是, Mock期待的不是返回值, Mock期待的是動作的執行. 它是依賴項的動態包裝, 它可以對哪個方法以什麼樣的順序被待測試系統(SUT)呼叫的這個期待行為進行預程式設計. 也就是說被測試的系統只有按照特定的順序呼叫mock依賴項的特定方法, 那麼該系統才算測試通過.

還有其它的一些術語就不介紹了, 主要是這四個.

對於Stub 和 Mock ,可以看下面兩張圖例:

 

Moq

官網: https://github.com/moq/moq4

Moq框架可以用來建立dummy, stub 和 mock. 在本文裡把這三個東西都叫做mock物件吧.

Moq使用一套API來建立stub和mock物件.

 

準備專案

一個簡單的.NET Core控制檯專案: https://github.com/solenovex/Moq-Tutorial-Code, 程式碼是裡面的01 before.

該專案非常簡單, 是關於球員轉會業務, 它目前只有三個類. 

TransferApplication, 球員轉會申請類:

TransferResult, 轉會審批結果列舉:

 

還有TransferApproval, 轉會審批類:

'

當前的邏輯是, 發起球員轉會申請後, 進行審批: 如果總費用大於預算, 那麼就直接拒絕; 如果總費用不超標, 並且球員小於30歲, 那麼就批准; 但如果球員大於30歲, 並且是超級巨星的話, 這將由老闆決定.

 

建立單元測試專案

在解決方案裡建立一個xUnit型別的專案:

 

然後要保證該專案所用到的庫都保持最新:

 

最後別忘了新增對FootballManager專案的引用:

 

開啟Text Explorer, 可以看到裡面有一個待測的單元測試:

 

做一個簡單的單元測試

把UnitTest1改成下面這個簡單的單元測試:

重新Build後, 可以看到單元測試的名稱更新了.

 

點選Run All, 執行單元測試, 結果成功:

 

隨後再新增一個簡單的單元測試:

 

Build, 後就會出現這個測試:

 

Run All, 測試也會成功:

 

新增依賴

這時, 有一些需求的變化, 球員轉會審批前, 需要通過體檢.

首先在轉會申請類裡面新增兩個球員的屬性:

 

然後新增一個體檢的介面:

這兩個方法的作用是一樣的, 但是呼叫方法略有不同.

 

但是此時, 該介面的實現類還沒有開發完畢:

 

在轉會審批類裡面, 需要新增這個依賴, 使用的是介面:

 

在單元測試類裡面, 我為轉會球員新增了這兩個屬性, 但是審批類會報錯, 因為沒有加入依賴項:

 

所以測試的時候需要注入這個依賴項IPhysicalExamination, 但是PhysicalExamination類還沒有做完(裡面的方法都沒有實現), 所以我們無法new出來這個類.

這時, 我們也許可以傳null進去?

 

這時, 專案是不報錯了.

跑單元測試, Run All:

測試失敗, 丟擲NullReferenceException. 而這個異常導致了測試無法正常進行.

 

所以, 我們需要Moq, 它可以提供一個Mock(模擬)版本的IPhysicalExamination, 並把它傳遞到審批類的建構函式裡.

 

安裝Moq

在單元測試專案新增Moq:

 

Moq的第一篇先到這.

 

相關文章