測試邊界 (The Test Boundary)#
對於軟體架構師而言,測試(Unit Test, Integration Test, etc.)不僅僅是用來找 Bug 的工具,它們也是系統的一個元件。 所有的測試都具備相同的架構特徵:
- 遵循依賴原則: 測試非常具體且詳細,它們總是依賴於被測試的程式碼。在架構圖中,測試位於最外層(比 UI 和框架更外層)。
- 可獨立部署: 測試通常部署在測試環境中,而非生產環境。
一、脆弱的測試問題 (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,我們可以保護測試免受系統結構變更的影響,確保測試是幫助我們重構的朋友,而不是阻礙變更的敵人。