第一篇文章, 關於Mock的概念介紹: https://www.cnblogs.com/cgzl/p/9294431.html
本文介紹使用Moq來Mock方法.
使用的程式碼: https://github.com/solenovex/Moq4-Tutorial-Code 裡面的 02 Before 部分.
Mock 物件
緊接著上文中的例子. 上一篇文章, 我在單元測試的時候, 把依賴項設為null:
然後便出現了NullReferenceException, 導致測試無法正常執行.
首先應該做的是在TransferApproval的建構函式裡判斷引數是否為null, 如果為null的話應該丟擲ArgumentNullException:
這是更恰當的異常.
這樣的話, 在測試的時候, 丟擲的就是ArgumentNullException了, 它可以更恰當的表達程式出現的問題:
現在我們可以使用mock版本的依賴項來代替null了:
上面的程式碼首先使用Moq建立了一個mock版本的IPhysicalExamination的例項.
而由於Moq對依賴項進行了包裝, 所以要獲得實際的mock依賴項, 我們需要使用mockExamination.Object屬性. 而這個屬性的型別就是IPhysicalExamination.
另外一個測試方法我也這麼改一下, 然乎重新Build. Run All Tests:
還是紅色的, 但現在是測試沒通過, 並不是丟擲異常.
測試沒通過的意思就是期待值和實際返回值不符.
讓我們來除錯一下這個測試, 我在TransferApproval類裡面設定一個端點, 檢視一下這個mock依賴項的方法返回值:
然後除錯測試:
跑到斷點
可以看到這個Mock版本依賴項的IsHealthy()方法的返回值是false.
我並沒有對這個Mock版本的IPhysicalExamination的IsHealthy()方法設定返回值, 正因為如此, 它才會返回它方法返回型別的預設值, 它的返回型別是bool, 而bool的預設值是false, 所以現在IsHealthy()方法在沒有設定的情況下的返回值就是false.
It類
而PhysicalExamination這個具體的實現類由於各種原因導致還沒有實現, 為了讓它不妨礙我們的單元測試, 我先設定讓它在無論傳進什麼引數的情況下都會返回true.
從業務上來講就是假設所有轉會球員都可以通過體檢:
那麼現在所有的測試都應該可以通過了:
這裡用到了It這個類, 在Moq裡, It這個類是用來做引數匹配的, it 就是"它"的意思, 它就代表需要被匹配的引數.
It.IsAny<T>(), 它表示傳遞給方法的引數的型別只要是T就可以, 值是任意的. 只要滿足了這個條件, 那麼方法的返回值就是後邊Returns()方法裡設定的值.
Moq 關於It類的文件: http://www.nudoq.org/#!/Packages/Moq/Moq/It
它有下面幾種用法:
- Is<TValue>(Expression<Func<TValue, Boolean>>)
- IsAny<TValue>()
- IsIn<TValue>(IEnumerable<TValue>)
- IsInRange<TValue>(TValue, TValue, Range)
- IsNotIn<TValue>(IEnumerable<TValue>)
- IsNotNull<TValue>()
- IsRegex(string)
我認為通過方法名就可以知道這些方法的用途.
下面我修改一下該測試方法, 使用It其它幾個方法:
其測試結果仍然是通過的.
嚴謹(Strict) vs 寬鬆(Loose) Mock
Moq裡面有Strict(嚴謹)和Loose(寬鬆) mock物件的概念, 當然也有很多人不喜歡這個概念.
在當前的測試方法裡, TransferApproval依賴於Mock<IPhysicalExamination>, 並呼叫其IsHealthy()方法.
如果不對IsHealthy()方法進行任何設定的情況下, 方法會返回bool的預設值false, 這種就是loose(寬鬆) Mock.
在建立Mock物件的時候, 還可選傳遞一個MockBehavior這個引數.
MockBehavior是一個列舉, 它有三個值:
- MockBehavior.Strict, 如果mock物件上的方法沒有被預先設定好, 那麼測試中呼叫該方法的時候就會丟擲異常.
- MockBehavior.Loose, 即使方法沒有被預先設定, 呼叫它的時候也不會丟擲異常. 它會返回該方法返回型別的預設值.
- MockBehavior.Default, 它代表MockBehavior.Loose.
如果上例使用Strict Mock, 那麼將會丟擲Exception:
下面我把一個測試改為Strict Mock, 並取消了對IsHealthy()方法的設定:
而測試時會丟擲MockException:
在對方法進行設定後, 測試就會通過:
可以感覺到:
Loose Mock, 可以少寫一些設定程式碼, 可以返回預設值, 不易讓測試中斷
Strict Mock, 需要寫跟多的設定程式碼, 每個被呼叫的方法都需要進行設定, 所以也更容易讓測試中斷.
Moq的建議是: 大多數情況下應該使用Loose Mock, 只有特殊需要的時候才去使用Strict Mock.
out引數
修改一下TransferApproval類的轉會審批方法:
這次使用的是帶有out引數的IsHealthy()方法.
建立一個測試方法, 並設定這個帶有out引數的方法:
很簡單, 測試會通過:
完成的程式碼在: https://github.com/solenovex/Moq4-Tutorial-Code 02 After
未完待續....