為什麼單元測試的目標從類改為依賴行為? - miro

banq發表於2022-01-09

類級別的測試有以下主要問題:
  1. 類測試使更改變得痛苦
  2. 類測試不驗證實際行為
  3. 類測試很難理解

  

類測試使更改變得痛苦
當對我們的程式碼進行更改時,這會成為一個問題,因為每個小的修改都會破壞測試。由於對程式碼庫的典型更改會影響多個類,因此我們通常必須為我們接觸的每個類更新測試。不僅如此,我們可能還需要更新其他模擬任何已更改類的測試。這變得單調乏味,甚至為改變最細微的細節增加了額外的障礙。
即使我們只修改內部實現細節,我們的測試也會中斷並需要更新。假設我們想將一個類重構為兩個類,以便我們可以在其他地方重用部分邏輯。這將立即中斷測試,要求我們刪除和更新原始類的測試用例,併為新增的類建立一組新的測試用例。我們甚至沒有改變系統的任何外部行為。
相反,我們希望測試僅在外部行為發生變化時才中斷。這將使我們可以自由地對我們的程式碼庫進行任何內部重構,而無需對我們的測試進行任何更改。
  

類測試不驗證實際行為
類級測試側重於孤立的各個類。因此,我們正在測試實現細節,而不是整個程式碼的行為。一個主要的缺點是,每當測試失敗時,它都不會告訴我們程式碼的外部行為是否發生了變化,因為測試可能只是由於實現細節的改變而失敗了。
當所有測試在更改後繼續為綠色時,這甚至是一個問題——通常向開發人員表明一切都很好,並且可以安全部署。然而,這種型別的測試通常不是這種情況,因為我們依賴於模擬其他類。每次模擬一個類時,都會假設該類是如何工作的,當類本身發生變化並且我們忘記更新mack時,這種假設很快就會過時無效。
這意味著即使類測試在更改後繼續保持綠色,我們也無法確定整個程式碼實際上是否正確執行。相反,我們希望測試透過或失敗僅與程式碼庫的外部行為相關聯。如果測試失敗,則行為已更改,如果測試透過,則程式碼的行為相同。
 

類測試很難理解
我們剛剛介紹了孤立的測試類並不能說明我們程式碼的外部行為。因此,要真正瞭解進行更改後流程是否正常工作,我們需要了解流程中涉及的每個類,以及它們相應的測試是否涵蓋了所有必需的案例以及它們所使用的類的所有可能結果。
然後,我們必須在腦海中將其拼湊起來,以得出各個類是否會共同導致流的正確外部行為的結論。這既困難又容易出錯,尤其是當更改是由不熟悉程式碼庫每個角落的人進行時。
更復雜的事實是,類測試由於其對實現細節的關注,導致許多測試經常中斷,並且由於許多不同的原因。這意味著開發人員需要不斷地更新測試,每次都需要全面瞭解流程中涉及的類、它們的測試以及更改後它們如何以不同的方式相互影響。
讓我們看一下下面的示例,該示例顯示了四個相互使用的類:
如果類D發生變化,我們不僅要了解和更新 的測試D,還需要了解它對所有類的影響D及其對應的測試。
在此示例中,這將是類B和C. 此外,B 由於D變化可能導致行為不同,我們還需要了解A該類的測試。
儘管理解單個類測試可能並不困難,但當我們需要從這些測試中推​​斷出任何型別的外部行為時,它就會變得相當複雜。
相反,我們更希望一個單獨的測試用例就足以推斷出我們程式碼庫的某些實際外部行為。
 

替代方案
我們自然需要一種替代的自動化測試方法。我們主要將這些概念應用於測試單個微服務,但它們也可以應用於許多其他型別的系統,例如本機和 Web 應用程式甚至庫。
我們最終得到的結果依賴於將我們的執行系統視為只關注外部行為的黑匣子的基本概念。這意味著,作為測試的一部分,我們啟動系統並在系統執行時對其執行每個測試。我們的目標是儘可能將其視為一個黑盒,因為這會自動使測試獨立於實現細節並專注於行為。本質上,我們將整個系統(例如微服務)視為被測單元或系統,而不是單元測試類。
這已成為我們最細粒度的測試型別,並且仍然只關注單個系統或程式碼庫的行為。可能需要其他型別的測試來確保跨多個系統的端到端正確行為。
 

模擬外部依賴
將我們的系統視為黑盒時,我們不再希望模擬、存根、複製或偽造程式碼庫的任何內部部分,因為測試不應該關心這些。
不過,我們確實想模擬外部依賴項,因為這允許我們單獨測試我們的系統。根據什麼是有意義的,什麼是外部的,什麼不是外部的定義會因專案而異。
例如,在測試我們的微服務時,資料庫被視為內部資料庫,因此不會被模擬。時間被視為外部元件並被模擬,系統與其他外部系統之間的 HTTP 通訊也是如此。在我們的微服務使用訊息佇列的情況下,兩者都可以。
微服務本身釋出和使用的訊息不會被模擬,但是,當向其他系統釋出或接收任何訊息或從其他系統接收任何訊息時,這些訊息將被模擬並視為外部訊息。
 

透過呼叫外部公開的端點來實現測試
將正在執行的系統視為黑盒時,我們希望安排測試資料並提供與在真實環境中執行時完全相同的輸入。
對於微服務,這可以透過呼叫它公開的端點,或在服務使用的外部佇列上釋出訊息。
對於前端,這可能是透過實際按下按鈕和導航使用者介面,類似於使用者會做的事情。
使用這種方法,我們確保所有測試都基於真實的應用程式狀態,就像它們在生產中出現的那樣。此外,由於一切都是透過允許的系統輸入來呼叫的,我們永遠不會花時間測試現實中不可能發生的案例,這是一個額外的好處。
為了使測試易於編寫和維護,建立可重用的方法來安排常用的測試資料通常是非常值得的。一個例子可以是在資料庫中安排使用者。與其在每個測試中發出建立使用者的 HTTP 請求,不如將其移至每個測試用例都可以呼叫的可重用方法。
 

斷言結果
安排好測試資料後,我們準備在我們的系統上執行操作並斷言結果。由於我們仍然將我們的系統視為一個黑匣子,我們的目標是隻斷言我們的行為導致的外部結果。
如果我們的操作是 HTTP 請求,外部結果的示例可能是 HTTP 響應。此外,外部結果也可能是系統發出的傳出 HTTP 呼叫以及在外部訊息佇列上釋出的訊息。
在設定模擬以斷言我們系統的正確外部行為時,請考慮在嚴格模式下使用模擬。從某種意義上說,它們應該是嚴格的,因為當使用任何未專門設定為處理的輸入呼叫時,它們會導致測試失敗。這不僅可以確保我們的系統做正確的事情,而且不會發生意外行為。我們不想在不應該的時候進行 HTTP 呼叫並將訊息傳送到其他系統。
 

相關文章