簡體   English   中英

什么時候使用 Mockito.verify()?

[英]When to use Mockito.verify()?

我為 3 個目的編寫 jUnit 測試用例:

  1. 為了確保我的代碼滿足所有(或大部分)輸入組合/值下的所有必需功能。
  2. 確保我可以更改實現,並依靠 JUnit 測試用例告訴我我的所有功能仍然滿足。
  3. 作為我的代碼處理的所有用例的文檔,並作為重構的規范 - 如果代碼需要重寫。 (重構代碼,如果我的 jUnit 測試失敗 - 您可能錯過了一些用例)。

我不明白為什么或何時應該使用Mockito.verify() 當我看到調用verify() ,它告訴我我的 jUnit 正在意識到實現。 (因此更改我的實現會破壞我的 jUnit,即使我的功能不受影響)。

我在找:

  1. 適當使用Mockito.verify()的准則應該是什么?

  2. jUnits 知道或緊密耦合到被測類的實現是否從根本上是正確的?

如果類 A 的契約包括它調用類型 C 對象的方法 B 的事實,那么您應該通過創建類型 C 的模擬來測試這一點,並驗證方法 B 已被調用。

這意味着類 A 的契約有足夠的細節來討論類型 C(可能是接口或類)。 所以,是的,我們談論的規范級別不僅僅是“系統要求”,而且在某種程度上描述了實現。

這對於單元測試來說是正常的。 當您進行單元測試時,您希望確保每個單元都在做“正確的事情”,這通常包括它與其他單元的交互。 此處的“單元”可能是指類或應用程序的更大子集。

更新:

我覺得這不僅適用於驗證,也適用於存根。 一旦您存根合作者類的方法,您的單元測試就在某種意義上變得依賴於實現。 單元測試的本質就是這樣。 由於 Mockito 與驗證一樣重要,因此您完全使用 Mockito 的事實意味着您將遇到這種依賴關系。

根據我的經驗,如果我改變一個類的實現,我經常不得不改變它的單元測試的實現來匹配。 通常情況下,雖然,我不會改變什么單元測試該類的庫存; 當然,除非更改的原因是存在我之前未能測試的條件。

所以這就是單元測試的意義所在。 不受這種對協作者類使用方式的依賴的測試實際上是子系統測試或集成測試。 當然,這些也經常用 JUnit 編寫,並且經常涉及到模擬的使用。 在我看來,“JUnit”是一個糟糕的名字,因為它是一個讓我們產生所有不同類型測試的產品。

大衛的回答當然是正確的,但並沒有完全解釋為什么你會想要這個。

基本上,在進行單元測試時,您是在單獨測試一個功能單元。 您測試輸入是否產生預期的輸出。 有時,您還必須測試副作用。 簡而言之,verify 允許您這樣做。

例如,您有一些業務邏輯應該使用 DAO 存儲事物。 您可以使用集成測試來實現這一點,該測試實例化 DAO,將其連接到業務邏輯,然后在數據庫中查看是否存儲了預期的內容。 那不再是單元測試了。

或者,您可以模擬 DAO 並驗證它是否以您期望的方式被調用。 使用 mockito,你可以驗證某個東西是否被調用,它被調用的頻率,甚至可以在參數上使用匹配器來確保它以特定的方式被調用。

像這樣的單元測試的另一面確實是您將測試與實現聯系起來,這使得重構有點困難。 另一方面,良好的設計氣味是正確執行它所需的代碼量。 如果您的測試需要很長時間,則可能是設計出了問題。 因此,具有許多需要測試的副作用/復雜交互的代碼可能不是一件好事。

這是個好問題! 我認為它的根本原因如下,我們不僅使用 JUnit 進行單元測試。 所以這個問題應該拆分:

  • 我應該在我的集成(或任何其他高於單元的測試)測試中使用 Mockito.verify() 嗎?
  • 我應該在我的黑盒單元測試使用 Mockito.verify() 嗎?
  • 我應該在白盒單元測試使用 Mockito.verify() 嗎?

所以如果我們忽略高於單元的測試,這個問題可以改寫為“使用白盒單元測試和 Mockito.verify() 在單元測試和我的可能實現之間創造了很好的結合,我可以做一些“灰盒“單元測試以及我應該使用什么經驗法則”。

現在,讓我們一步一步地完成所有這些。

*- 我應該在我的集成(或任何其他高於單元的測試)測試中使用 Mockito.verify() 嗎?* 我認為答案顯然是否定的,而且你不應該為此使用模擬。 您的測試應盡可能接近實際應用。 您正在測試完整的用例,而不是應用程序的孤立部分。

*黑盒vs白盒單元測試* 如果你使用黑盒方法你真正在做什么,你提供(所有等價類)輸入、狀態和測試,你將收到預期的輸出。 在這種方法中,一般使用模擬是合理的(你只是模仿他們在做正確的事情;你不想測試它們),但調用 Mockito.verify() 是多余的。

如果你正在使用白盒方法你真正在做什么,你正在測試你的單元的行為 在這種方法中,調用 Mockito.verify() 是必不可少的,您應該驗證您的單元的行為是否符合您的預期。

灰盒測試的經驗法則 白盒測試的問題在於它會產生高耦合。 一種可能的解決方案是進行灰盒測試,而不是白盒測試。 這是一種黑白盒測試的組合。 您實際上是在測試單元的行為,就像在白盒測試中一樣,但一般而言,您在可能的情況下使其與實現無關。 如果可能,您將像在黑盒情況下一樣進行檢查,只需斷言輸出是您所期望的。 所以,你的問題的本質是什么時候有可能。

這真的很難。 我沒有很好的例子,但我可以給你舉個例子。 在上面提到的 equals() 與 equalsIgnoreCase() 的情況下,您不應調用 Mockito.verify(),只需斷言輸出即可。 如果你做不到,把你的代碼分解成更小的單元,直到你能做到。 另一方面,假設您有一些 @Service 並且您正在編寫本質上是 @Service 包裝器的 @Web-Service - 它會將所有調用委托給 @Service (並進行一些額外的錯誤處理)。 在這種情況下,調用 Mockito.verify() 是必不可少的,您不應該重復您為 @Serive 所做的所有檢查,驗證您正在使用正確的參數列表調用 @Service 就足夠了。

我必須說,從經典方法的角度來看,您是絕對正確的:

  • 如果您首先創建(或更改)應用程序的業務邏輯,然后用(采用)測試Test-Last 方法覆蓋它,那么讓測試了解您的軟件如何工作的任何信息將是非常痛苦和危險的,除了檢查輸入和輸出。
  • 如果您正在實踐測試驅動方法,那么您的測試將首先被編寫、更改並反映您的軟件功能的用例 實現取決於測試。 這有時意味着,您希望您的軟件以某種特定方式實現,例如依賴於其他組件的方法,甚至調用它特定的次數。 這就是Mockito.verify()派上用場的地方!

重要的是要記住,沒有通用的工具。 軟件的類型、規模、公司目標和市場情況、團隊技能和許多其他因素會影響在您的特定情況下使用哪種方法的決定。

正如一些人所說

  1. 有時您沒有可以斷言的直接輸出
  2. 有時您只需要確認您的測試方法是否將正確的間接輸出發送給其合作者(您正在嘲笑)。

關於在重構時破壞測試的擔憂,在使用模擬/存根/間諜時在某種程度上是預料之中的。 我的意思是根據定義而不是關於特定的實現,例如 Mockito。 但是你可以這樣想-如果你需要做的是將創建的道路上重大變化的重構你的方法的作品,這是一個好主意,做一個TDD的方式,這意味着你可以改變你的測試定義新行為(這將使測試失敗),然后進行更改並再次通過測試。

在大多數情況下,當人們不喜歡使用 Mockito.verify 時,這是因為它用於驗證被測試單元所做的一切,這意味着如果有任何變化,您將需要調整測試。 但是,我認為這不是問題。 如果您希望能夠在不需要更改測試的情況下更改方法的功能,那基本上意味着您要編寫不測試您的方法所做的一切的測試,因為您不希望它測試您的更改. 這是錯誤的思維方式。

真正有問題的是,如果您可以修改您的方法所做的事情,並且應該完全涵蓋該功能的單元測試不會失敗。 這意味着無論您更改的意圖是什么,您的更改結果都不會被測試覆蓋。

正因為如此,我更喜歡盡可能地模擬:也模擬你的數據對象。 這樣做時,您不僅可以使用 verify 檢查是否調用了其他類的正確方法,還可以通過這些數據對象的正確方法收集傳遞的數據。 為了使其完整,您應該測試調用發生的順序。 例子:如果你修改了一個db實體對象,然后使用repository保存它,僅僅驗證對象的setter調用的數據是否正確,並且repository的save方法被調用是不夠的。 如果它們以錯誤的順序調用,您的方法仍然無法執行它應該執行的操作。 所以,我不使用 Mockito.verify 但我創建了一個包含所有模擬的 inOrder 對象並使用 inOrder.verify 代替。 如果你想讓它完整,你還應該在最后調用 Mockito.verifyNoMoreInteractions 並將所有模擬傳遞給它。 否則,有人可以在不測試的情況下添加新功能/行為,這意味着在您的覆蓋率統計數據可以是 100% 之后,您仍然在堆積未斷言或驗證的代碼。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM