概述#

Testability(可測試性)是指軟體在特定的測試情境下,能多容易地展現其缺陷的程度。測試的核心目標並非證明軟體沒有錯誤,而是在合理的時間與成本下,盡可能地發現潛在的缺陷。

測試的基本模型包含幾個關鍵元素:將輸入(Input)送入程式(Program),觀察其輸出(Output)與內部狀態(Internal State),再由測試預言機(Oracle)判斷結果是通過還是失敗。測試預言機可以是人工檢查、預期值比對、或自動化判斷邏輯。

Figure 12.1: A model of testing

測試基礎建設(Test Infrastructure)本身可以是相當龐大的軟體工程,包括測試裝具(Test Harness)——用於產生介面測試資料、模擬外部環境、或在生產環境中運行的獨立軟體。測試裝具及其伴隨的基礎設施擁有自己的架構、利害關係人與品質屬性需求。

Netflix 的 Simian Army 是測試基礎建設的經典案例。Netflix 在 Amazon EC2 雲端上使用一系列服務進行測試:Chaos Monkey 隨機終止執行中的程序以測試系統韌性;Latency Monkey 注入人為延遲模擬服務降級;Conformity Monkey 識別不符合最佳實踐的實例;Security Monkey 尋找安全漏洞並終止違規實例。這說明某些系統過於複雜且具適應性,無法完全測試,部分行為是湧現(Emergent)的。

可測試性通用情境(Testability General Scenario)#

可測試性的通用情境描述了測試的六個面向:

  • 來源(Source):測試案例可由人類或自動化測試工具執行,包括單元測試人員整合測試人員系統測試人員驗收測試人員終端使用者
  • 刺激(Stimulus):一組測試被啟動,目的為驗證系統功能、驗證品質屬性、或發現對品質的新興威脅
  • 環境(Environment):測試發生在各種事件或生命週期里程碑,包括完成編碼增量(類別、層、服務)、完成子系統整合、完成整體系統實作、部署至生產環境、交付給客戶、或按排程測試
  • 成品(Artifact):被測試的部分,包括程式碼單元(對應架構中的模組)、元件、服務、子系統、整體系統,以及測試基礎建設本身
  • 回應(Response):系統及其測試基礎建設可被控制以執行測試並觀察結果,包括執行測試套件並擷取結果、擷取導致故障的活動、控制與監控系統狀態
  • 回應度量(Response Measure):發現故障或故障類別的努力程度、達到特定狀態空間涵蓋率的努力程度、下一次測試揭露故障的機率、測試執行時間、偵測故障的努力程度、準備測試基礎建設的時間、將系統帶至特定狀態所需的努力、風險曝露的降低程度(損失大小 x 損失機率)

Figure 12.2: Sample testability scenario

具體情境範例:開發人員在開發階段完成一個程式碼單元,執行測試序列並擷取結果,在 30 分鐘內達到 85% 的路徑涵蓋率。

可測試性戰術(Tactics for Testability)#

可測試性戰術旨在促進更容易、更有效率且更有能力的測試。相較於可修改性、效能與可用性等品質屬性,可測試性的架構技術在過去較少受到關注,但考量到測試的高昂成本,架構師為降低測試成本所做的任何努力都會帶來顯著效益。

Figure 12.3: The goal of testability tactics

可測試性戰術分為兩大類:第一類處理為系統增加可控制性(Controllability)與可觀察性(Observability);第二類處理限制系統設計的複雜度

控制與觀察系統狀態(Control and Observe System State)#

控制與觀察是可測試性的核心——控制某些東西如果無法觀察結果就毫無意義。最簡單的形式是提供輸入、讓元件運作、然後觀察輸出。但此類戰術更進一步,讓元件維護狀態資訊,允許測試者設定狀態值,並讓資訊在需要時可被存取。

  • Specialized Interfaces(專用介面):提供專用的測試介面以控制或擷取元件的變數值,包括:
    • Get/Set 方法:取得與設定重要變數、模式或屬性
    • Report 方法:回傳物件的完整狀態
    • Reset 方法:將內部狀態設定為指定狀態
    • Verbose 輸出:開啟詳細輸出、各層級事件記錄、效能儀器化或資源監控

在效能關鍵與安全關鍵系統中,部署與測試不同的程式碼是有問題的。如果移除測試程式碼,如何確保發佈版本具有相同的行為(特別是時序行為)?因此專用介面策略更適合其他類型的系統。

  • Record/Playback(記錄/回放):記錄跨介面的狀態,以便重現導致故障的情境。Record 擷取跨介面的資訊,Playback 使用它作為進一步測試的輸入
  • Localize State Storage(集中狀態儲存):若要將系統、子系統或元件啟動到任意狀態進行測試,最方便的做法是將狀態儲存在單一位置。使用狀態機(State Machine)是追蹤與報告當前狀態的便利方式
  • Abstract Data Sources(抽象資料來源):將介面抽象化以便於替換測試資料。例如設計架構使測試系統能輕鬆指向不同的測試資料庫或測試資料檔案,而無需更改功能程式碼
  • Sandbox(沙箱):將系統實例與真實世界隔離,使實驗不受後果擔憂的約束。沙箱可用於情境分析、訓練與模擬。常見形式是虛擬化資源——建構行為可控的資源版本(如抽象系統時間以超越實際時鐘速度測試關鍵時間邊界)。StubMockDependency Injection 都是簡單但有效的虛擬化形式
  • Executable Assertions(可執行斷言):在程式的特定位置手動放置斷言,檢查資料值是否滿足指定約束。斷言可表示為方法的前置/後置條件以及類別級別的不變式。若斷言涵蓋測試案例,它們實質上是將測試預言機嵌入程式碼

除了上述戰術外,還有幾種技術可用於替換元件以便於測試:元件替換(Component Replacement,透過建置腳本替換實作)、前處理器巨集(Preprocessor Macros,展開為狀態回報程式碼)、切面(Aspects,處理狀態回報的橫切關注點)。

限制複雜度(Limit Complexity)#

複雜的軟體更難測試——其操作狀態空間大,在大型狀態空間中重現精確狀態比在小型空間中更困難。由於測試不僅是讓軟體失敗,更要找出導致失敗的故障以便移除,因此行為的可重現性至關重要。

  • Limit Structural Complexity(限制結構複雜度)

    • 避免或解決元件間的循環依賴
    • 隔離並封裝對外部環境的依賴
    • 降低元件間的耦合度
    • 在物件導向系統中:限制繼承深度、限制多型與動態呼叫
    • 類別回應度(Response of a Class):C 類別的方法數加上 C 的方法所呼叫的其他類別方法數,此指標已被實證證明與可測試性相關
    • 確保系統具有高內聚低耦合關注點分離(這些也是可修改性戰術)
    • 分層模式有助於測試:先測試底層,再以對底層的信心測試上層
  • Limit Nondeterminism(限制不確定性)

    • 找出所有不確定性來源(如無約束的平行處理)並盡可能消除
    • 某些不確定性不可避免(如回應不可預測事件的多執行緒系統),可使用其他戰術(如 Record/Playback)來管理

Figure 12.4: Testability tactics

可測試性戰術問卷(Tactics-Based Questionnaire)#

分析師可逐一詢問以下問題,記錄系統是否支援(Y/N)、風險、設計決策與假設:

控制與觀察系統狀態類:

  • 系統是否具有專用介面以取得與設定變數值?
  • 系統是否具有記錄/回放機制?
  • 系統的狀態儲存是否集中化?
  • 系統是否將資料來源抽象化?
  • 系統的部分或全部是否可在沙箱中運作?
  • 系統中是否有可執行斷言的角色?

限制複雜度類:

  • 系統是否以系統化的方式限制結構複雜度?
  • 系統中是否存在不確定性,是否有方法控制或限制此不確定性?

可測試性模式(Patterns for Testability)#

可測試性模式的共通目標是將測試專用程式碼與系統的實際功能解耦。

Dependency Injection(依賴注入)#

此模式將客戶端的依賴關係與其行為分離,利用控制反轉(Inversion of Control)原則。包含四個角色:

  • Service(服務):希望廣泛提供的服務
  • Client(客戶端):服務的使用者
  • Interface(介面):客戶端使用、服務實作的介面
  • Injector(注入器):建立服務實例並注入客戶端

當介面建立服務並注入客戶端時,客戶端無需知道具體實作,所有實作細節在執行時期注入。

  • 優點:可注入測試實例(而非生產實例),管理與監控服務的狀態。客戶端無需知道如何被測試,這正是許多現代測試框架的實作方式
  • 權衡:使執行時期效能較不可預測;增加少量前期複雜度;可能需要開發人員重新學習控制反轉思維

Strategy Pattern(策略模式)#

此模式允許在執行時期改變類別的行為,常用於多種演算法可執行同一任務的情境。類別包含一個抽象方法,具體版本基於情境因素動態選擇。此模式常用於以測試版本取代非測試版本,提供額外輸出與內部健全性檢查。

  • 優點:使類別更簡單,不將多個關注點(如同一功能的不同演算法)合併到單一類別
  • 權衡:為簡單類別或少量執行時期選擇增加不必要的複雜度;對小型類別可能略微降低可讀性,但隨複雜度增長反而提升可讀性

Intercepting Filter Pattern(攔截過濾器模式)#

此模式在客戶端與服務之間的請求或回應中注入前處理與後處理。可定義任意數量的過濾器並以任意順序套用。例如,日誌記錄與驗證服務作為過濾器實作一次即可普遍套用。測試過濾器可以此方式插入,不干擾系統中的其他處理。

  • 優點:使類別更簡單,不將所有前/後處理邏輯放入類別中;強力促進重用,大幅減少程式碼量
  • 權衡:若傳遞大量資料,此模式可能高度低效並增加顯著延遲,因為每個過濾器都會完整處理整個輸入

可測試性與可修改性密切相關。高內聚、低耦合與關注點分離等可修改性戰術同時也有助於可測試性。分層架構模式更是天然適合測試——先驗證底層的正確性,再以此為基礎測試上層。

延伸閱讀#

  • [Binder 00] 提供了測試的良好概述
  • Jeff Voas 關於可測試性與可測試性和可靠性之間關係的基礎工作值得研究([Voas 95])
  • Bertolino 和 Strigini 開發了 Figure 12.1 所示的測試模型
  • Robert C. Martin 的 Clean Architecture 深入探討了測試驅動開發與架構之間的關係
  • Kent Beck 的 Test-Driven Development by Example 是測試驅動開發的早期權威參考