引言#
太多專案僅仰賴手動驗收測試來確認軟體是否符合功能與非功能需求。即使存在自動化測試,也常常維護不善、過時,仍需大量手動測試來補充。
W. Edwards Deming 的十四要點之一:「停止依賴大規模檢驗來達成品質,改善流程,從一開始就將品質內建於產品之中。」 測試是一種跨職能的活動,涉及整個團隊,應從專案一開始就持續進行。
內建品質(Building quality in) 意味著:
- 在多個層級撰寫自動化測試(單元、元件、驗收)
- 將測試作為 部署流水線(deployment pipeline) 的一部分,每次對應用程式、設定或執行環境的變更都會觸發
- 持續進行手動測試:展示會議(showcases)、可用性測試(usability testing)、探索性測試(exploratory testing)
測試策略的設計本質上是一個識別與排序專案風險、並決定如何緩解風險的過程。好的測試策略能帶來更少的 bug、降低的支援成本,以及最完整且即時的應用程式文件——以可執行規格(executable specification)的形式呈現。
測試的類型#
Brian Marick 提出了一個廣泛使用的 測試象限圖(Testing Quadrant),依據兩個維度對測試進行分類:
- 面向業務(Business-facing) vs. 面向技術(Technology-facing)
- 支援開發(Support programming) vs. 評審專案(Critique project)

Figure 4.1: Testing quadrant diagram, due to Brian Marick
mindmap
root((測試類型))
面向業務 + 支援開發
功能驗收測試
Happy Path / Alternate / Sad Path
面向技術 + 支援開發
單元測試
元件測試
部署測試
面向業務 + 評審專案
展示會議 Showcases
探索性測試
可用性測試
Beta / Canary 測試
面向技術 + 評審專案
非功能驗收測試
容量 / 安全 / 可用性面向業務、支援開發的測試#
即常見的 功能驗收測試(Functional Acceptance Tests),用來確保 story 的驗收標準被滿足。
核心特性:
- 驗收測試應在開發開始之前撰寫(理想上也自動化)
- 回答開發人員「我怎麼知道完成了?」以及使用者「我拿到我要的東西了嗎?」
- 現代工具如 Cucumber、JBehave、Concordion、Twist 將測試腳本與實作分離,讓使用者可以撰寫測試腳本,開發者和測試人員合力實作
Happy Path / Alternate Path / Sad Path:
| 路徑類型 | 說明 |
|---|---|
| Happy Path | 每個 story 的標準路徑,常使用 「Given-When-Then」 格式表達 |
| Alternate Path | 初始狀態、操作或最終狀態的變體,代表不同的使用情境 |
| Sad Path | 應觸發錯誤條件的路徑 |
透過等價分割分析(equivalence partitioning) 與邊界值分析(boundary value analysis) 減少測試案例數量。
驗收測試應在類生產環境(production-like environment) 中執行。自動化驗收測試的測試工具應以使用者相同的方式與應用程式互動。
自動化驗收測試的價值#
- 加快回饋循環——開發人員可直接執行測試確認需求是否完成
- 減輕測試人員工作量,釋出時間做更高價值的探索性測試
- 構成強大的迴歸測試套件(regression test suite),特別在大型應用或團隊中至關重要
- 使用行為驅動開發(BDD)的可讀測試名稱,可以自動產生需求文件
自動化驗收測試維護成本可能很高。做得不好會對交付團隊造成沉重負擔。應遵循良好實踐並使用適當工具來降低成本。
實務建議:
- 每個 story 至少要有一個自動化 happy path 驗收測試
- 自動化測試覆蓋率的建議策略:happy path 完整覆蓋,其餘重要部分有限覆蓋
- 良好的自動化覆蓋率代表單元、元件、驗收測試各自應覆蓋 80% 以上(不是三者加總 80%)
- 試金石測試:如果你替換系統某部分(例如持久層),執行自動化驗收測試通過後,你是否有信心系統仍然正常運作?
驗收測試是否應操作 UI?#
- 理想上驗收測試應透過 UI 執行,但多數 UI 測試工具會與 UI 緊密耦合,UI 稍有變動測試就壞
- 解決方案:
- 在測試與 UI 之間加入抽象層,降低 UI 變更時的維護成本
- 針對 UI 下方的公開 API 執行驗收測試(UI 本身不應包含業務邏輯),UI 測試縮減為少量 UI 本身的檢查
面向技術、支援開發的測試#
這些測試由開發人員獨自撰寫與維護,包含三種:
單元測試(Unit Tests)
- 在隔離環境下測試特定程式碼片段
- 常使用 test doubles 模擬其他部分
- 不應呼叫資料庫、使用檔案系統、與外部系統通訊
- 執行速度極快,至少覆蓋 80% 的程式碼路徑
元件測試(Component Tests)
- 測試較大的功能集群,能捕捉元件互動產生的 bug
- 速度較慢,可能涉及資料庫、檔案系統或其他系統的 I/O
- 有時稱為「整合測試」,但此詞已被過度使用
部署測試(Deployment Tests)
- 在每次部署時執行,確認應用程式正確安裝、正確設定、可連接所需服務且正常回應
面向業務、評審專案的測試#
這些手動測試驗證應用程式是否真正向使用者交付預期價值,不僅檢查是否符合規格,更檢查規格本身是否正確。
| 測試方法 | 說明 |
|---|---|
| 展示會議(Showcases) | 敏捷團隊在每個迭代結束時向使用者展示新功能,是確認工作真正完成的心跳。展示會議容易引發大量改善建議——及早獲得回饋遠勝於專案結束時才發現問題 |
| 探索性測試(Exploratory Testing) | James Bach 定義為「測試人員在執行測試的同時主動設計測試」,是一種創造性的學習過程,不僅發現 bug,也催生新的自動化測試與新需求 |
| 可用性測試(Usability Testing) | 發現使用者用軟體達成目標的難易程度,是驗證應用程式是否真正交付價值的終極測試 |
| Beta 測試與 Canary Releasing | 將不同版本部署給部分使用者,收集使用統計並淘汰不夠有效的功能,形成功能演進的方法 |
面向技術、評審專案的測試#
即非功能驗收測試(Nonfunctional Acceptance Tests),涵蓋容量、可用性、安全性等。
「非功能需求」這個名稱常被批評為不恰當(替代名稱如 cross-functional requirements 或 system characteristics),但無論如何稱呼,非功能驗收標準應與功能驗收標準以相同方式納入應用程式需求。
- 這類測試需要特殊環境與專業知識,通常執行時間較長
- 即使完全自動化,也傾向在部署流水線中較晚執行、頻率較低
- 建議:不論專案大小,都應在專案初期至少設置一些基本的非功能測試
Test Doubles(測試替身)#
自動化測試的關鍵技巧之一是在執行時以模擬版本替換系統的某些部分。依據 Gerard Meszaros 在《xUnit Test Patterns》中的術語:
| 類型 | 說明 |
|---|---|
| Dummy | 傳遞用但從未實際使用,通常只是填充參數列表 |
| Fake | 有實際可運作的實作,但走捷徑不適合生產環境(如 in-memory database) |
| Stub | 對測試中的呼叫提供預設的制式回應 |
| Spy | 在 Stub 基礎上額外記錄被呼叫的資訊(如記錄發送了多少封郵件) |
| Mock | 預先設定預期接收的呼叫規格,收到未預期呼叫會拋出例外,驗證時檢查是否收到所有預期呼叫 |
Mock 是最容易被濫用的 test double。常見錯誤是用 mock 斷言程式碼的內部運作細節而非與協作者的互動,導致測試既無意義又脆弱——實作一變,測試就壞。
實際情境與策略#
新專案#
新專案是實現理想測試實踐的最佳機會。此階段變更成本低,建立一些簡單的基本規則和測試基礎設施即可啟動持續整合。
起步需要:
- 選擇技術平台與測試工具
- 建立簡單的自動化建置
- 制定遵循 INVEST 原則 的 story(Independent、Negotiable、Valuable、Estimable、Small、Testable),附帶驗收標準
嚴格流程:
- 客戶、分析師、測試人員定義驗收標準
- 測試人員與開發人員合作,基於驗收標準自動化驗收測試
- 開發人員撰寫程式碼實現驗收標準
- 任何自動化測試失敗(無論單元、元件或驗收),開發人員優先修復
sequenceDiagram
participant 客戶/分析師
participant 測試人員
participant 開發人員
客戶/分析師->>測試人員: 定義驗收標準
測試人員->>開發人員: 協商自動化驗收測試
開發人員->>開發人員: 撰寫實作程式碼
開發人員-->>測試人員: 測試失敗時通知
測試人員->>開發人員: 協助修復
開發人員->>客戶/分析師: 展示成果
客戶/分析師-->>測試人員: 進行後續測試所有團隊成員(包括客戶和專案經理)都必須認同自動化測試的價值。盲目自動化撰寫不佳的驗收標準,是不可維護的驗收測試套件的主要原因之一。
專案中期#
面對大型、資源不足、快速變化的程式碼庫時:
- 從最常見、最重要、最高價值的使用案例開始引入自動化測試
- 與客戶對話確認真正的業務價值所在,用測試保護這些功能
- 自動化涵蓋較廣的 happy path 測試,填入盡可能多的欄位、按下盡可能多的按鈕
- 手動重複測試同一功能超過兩三次時就考慮自動化
- 若發現花費大量時間修復某些測試,確認對應功能是否仍在頻繁變動;如是,可暫時忽略該測試
- 時間緊迫時,使用多組測試資料來確保覆蓋率,而非撰寫複雜的互動腳本
遺留系統(Legacy Systems)#
Michael Feathers 在《Working Effectively with Legacy Code》中將遺留系統定義為沒有自動化測試的系統。簡單的經驗法則:測試你改動的程式碼。
處理步驟:
- 如果不存在,先建立自動化建置流程
- 建立自動化功能測試的骨架(scaffolding)
- 與使用者一起識別系統的高價值用途,建立覆蓋核心高價值功能的廣泛自動化測試(本質上是 smoke tests)
- 開始開發新 story 時,採用分層測試方法:
- 第一層:簡單快速的測試,確保不阻礙開發
- 第二層:針對特定 story 關鍵功能的測試
- 新行為應以新專案相同方式開發與測試
注意事項:
- 遺留系統的程式碼通常不夠模組化,一處變更可能影響其他區域
- 只在能交付價值的地方撰寫自動化測試
- 區分功能程式碼與支撐/框架程式碼——大多數迴歸 bug 源自框架程式碼的變更
- 若軟體需在多種環境中執行,自動化測試搭配自動化部署至類生產環境的價值特別高
整合測試(Integration Testing)#
當應用程式透過各種協定與外部系統互動,或本身由多個鬆耦合模組組成時,整合測試變得至關重要。
兩種執行情境:
- 系統在測試中連接真實外部系統(或服務提供者控制的副本)
- 系統連接你自己建立的 test harness(測試工具)
避免觸碰真實外部系統的方法:
- 用防火牆在測試環境中隔離外部系統存取
- 透過設定項讓應用程式與模擬版本通訊
何時需要自建 Test Harness#
- 外部系統仍在開發中但介面已定義
- 沒有可用的測試實例,或測試系統太慢/太多 bug
- 回應不具確定性(如股票行情)
- 外部系統難以安裝或需要手動 UI 操作
- 需要針對涉及外部服務的功能撰寫標準自動化驗收測試
- CI 系統的負載超過輕量測試環境的承受能力
Test Harness 的設計#
- 若外部系統有狀態,harness 需根據請求內容回傳不同回應
- 最高價值的是黑箱測試:考慮外部系統所有可能回應,為每種回應撰寫測試
- 必須模擬異常行為(Michael Nygard 在《Release It!》中的建議):
- 拒絕網路連線、接受後中斷、接受但不回應
- 回應極慢、回傳超大量資料、回傳垃圾資料
- 拒絕憑證、回傳例外、回傳格式正確但狀態不合的回應
- 可透過不同 port 監聽來模擬各種失敗模式
- 搭配 Circuit Breaker 和 Bulkheads 等模式強化應用程式
自動化整合測試可在部署時作為 smoke tests 重複使用,也可作為監控生產系統的診斷工具。若在開發期間識別到整合問題是風險(幾乎必然如此),開發自動化整合測試應是早期優先事項。
整合風險清單:
- 測試服務是否可用且效能足夠?
- 服務提供者是否有能力回答問題、修 bug、新增功能?
- 是否能存取生產版本的系統做容量或可用性測試?
- 服務 API 是否易於使用我們的技術存取?
- 是否需要自行撰寫與維護測試服務?
- 外部服務行為異常時,應用程式表現如何?
流程(Process)#
驗收測試的協作流程#
有效的驗收測試產出需要良好的團隊溝通。最佳做法是在每個迭代開始時(或 story 開發前一週)召開一次利害關係人會議:
- 客戶、分析師、測試人員一起確定最高優先級的測試場景
- 使用 Cucumber、JBehave 等工具以自然語言撰寫驗收標準,再撰寫程式碼使其可執行
- 至少當場撰寫覆蓋 happy path 的最簡單驗收測試
- 會後再補充更多資料集以提高覆蓋率
開發與測試的協作:
- 測試人員與開發人員應在開發開始前盡早討論驗收測試
- 開發人員完成某功能時,應立即請測試人員在同一台機器上檢視
- 避免開發與測試間的交接成為瓶頸——最糟的情況是開發人員已開始新 story,卻被前一個 story 的 bug 打斷
管理缺陷積壓(Defect Backlogs)#
理想情況下,bug 永遠不該被引入。若實踐 TDD 和 CI 並擁有完整的自動化測試,開發人員應能在測試人員或使用者發現前就捕捉到 bug。但探索性測試、展示會議和使用者仍不可避免地會發現 bug。
處理策略:
| 策略 | 說明 |
|---|---|
| 零缺陷策略(James Shore) | bug 一發現就立即修復,需要團隊結構支持測試人員及早發現 bug、開發人員即時修復 |
| 可視化與追蹤 | 不要只顯示「通過」或「失敗」,而是顯示通過數、失敗數、忽略數,並繪製趨勢圖張貼在顯眼處 |
| 將缺陷視同功能 | bug 與 feature 放入同一 backlog 排序優先級,由客戶決定修 bug 與做新功能的優先順序。分類為 critical、blocker、medium、low,綜合考量發生頻率、對使用者影響、是否有 workaround |
持續累積缺陷積壓是一條滑坡。幾個月後幾乎必然導致龐大的 bug 清單,其中一些永遠不會被修復、一些已不相關、一些對某些使用者至關重要卻淹沒在雜訊中。
總結#
- 測試不是獨立階段,而是所有參與軟體交付的人的責任,從專案開始就應持續進行
- 測試的核心目的是建立驅動開發、設計與發佈的回饋循環
- 最短的回饋循環來自每次變更時執行的自動化測試套件,涵蓋從單元測試到驗收測試的所有層級
- 自動化測試應搭配探索性測試與展示會議等手動測試
- 測試策略與**「完成的定義(Definition of Done)」** 密不可分——測試結果是專案規劃的基石