測試邊界 (The Test Boundary)#

對於軟體架構師而言,測試(Unit Test, Integration Test, etc.)不僅僅是用來找 Bug 的工具,它們也是系統的一個元件。 所有的測試都具備相同的架構特徵:

  1. 遵循依賴原則: 測試非常具體且詳細,它們總是依賴於被測試的程式碼。在架構圖中,測試位於最外層(比 UI 和框架更外層)。
  2. 可獨立部署: 測試通常部署在測試環境中,而非生產環境。

一、脆弱的測試問題 (The Fragile Tests Problem)#

如果測試沒有被視為架構的一部分來精心設計,就會發生「脆弱的測試問題」。

  • 系統僵化: 當開發者修改一個通用的元件(例如更改一個欄位名稱),導致數百個測試同時失敗。
  • 抗拒改變: 當「修復測試」的成本高於「修改程式碼」的成本時,團隊就會開始抗拒重構。系統因此腐化。

原因: 測試與系統結構產生了強烈的耦合(Structural Coupling)。測試知道太多系統內部的細節了。

二、解決方案:測試 API (The Testing API)#

為了避免測試變得脆弱,我們必須將「測試結構」與「應用程式結構」解耦。 方法是建立一個特定的 Testing API

  • API 的角色: 這是一組互動者(Interactors)和介面轉接器(Interface Adapters)的超集合(Superset)
  • 功能:
    • 允許測試直接驗證所有業務規則。
    • 繞過 UI: 測試不應該透過登入畫面來驗證業務邏輯。
    • 繞過安全性限制: 讓測試能直接存取狀態。
  • 目的: 隱藏應用程式的具體結構。當應用程式重構(如類別拆分、改名)時,只要 Testing API 保持不變,測試就不會壞。

[Image of Testing API architecture diagram decoupling tests from application structure]

三、具體與抽象的演化#

測試程式碼與產品程式碼遵循著相反的演化路徑,這也是為什麼它們需要被解耦:

  • 產品程式碼: 傾向於越來越抽象(Abstract)一般化(General),以重用邏輯並應對多變的需求。
  • 測試程式碼: 傾向於持續保持具體(Concrete)特定化(Specific),因為它們必須驗證特定的情境與數據。

總結: 不要讓你的測試依賴於易變的細節(如 DOM 結構或特定函數名稱)。 透過設計一層專屬的 Testing API,我們可以保護測試免受系統結構變更的影響,確保測試是幫助我們重構的朋友,而不是阻礙變更的敵人。