測試是最普遍的品質改善活動。本章聚焦於開發者測試(developer testing)——單元測試、元件測試與整合測試,屬於白箱測試(white-box testing)。測試的目的是偵測錯誤而非證明程式正確;除錯(診斷與修正根因)留待下一章討論。
22.1 開發者測試在軟體品質中的角色#
測試是品質計畫的重要環節,但不應是唯一環節。協同開發實踐(如審查)能找到更高比例的錯誤,且每個錯誤的成本不到測試的一半。個別測試步驟的缺陷偵測率通常低於 50%,組合後也常低於 60%。
測試對開發者而言是反直覺的:目標是「破壞」軟體;無法證明無錯;測試本身不改善品質(如同體重計不能幫你減肥);需假設程式碼有錯——Myers 的實驗中,經驗豐富的程式設計師平均只找到 15 個已知缺陷中的 5 個。
開發者測試應佔專案總時間的 8-25%。測試結果可評估可靠度、導引修正方向,以及長期追蹤常見錯誤類型。
22.2 開發者測試的推薦方法#
| 方法 | 說明 |
|---|---|
| 針對需求測試 | 確保每項需求都有對應測試案例,盡早規劃 |
| 針對設計測試 | 確保設計已被正確實作 |
| 使用基礎路徑測試與資料流測試 | 至少測試每一行程式碼 |
| 使用過往錯誤清單 | 根據歷史錯誤類型設計測試案例 |
先寫測試(test-first)是過去十年最有價值的實踐之一:不增加工作量(只是重新排序)、更早偵測缺陷、迫使先思考需求與設計、及早暴露需求問題。
開發者測試的侷限:偏好乾淨測試而非髒測試(成熟組織髒測試比例為 5:1);高估覆蓋率(自認 95%,實際 50-60%);多數人以語句覆蓋為滿足,但分支覆蓋才是更好的標準。
22.3 測試技巧錦囊#
窮舉測試不可能——即使簡單的姓名+地址+電話就有約 10^66 種組合。測試的藝術在於挑選最可能揭露錯誤的少數案例。
結構化基礎測試(Structured Basis Testing)#
計算最少測試案例數:從 1 開始,每遇到 if、while、for、and、or 各加 1,case 每分支加 1(無 default 再加 1)。這只確保所有程式碼被執行,無法涵蓋資料變化。
資料流測試(Data-Flow Testing)#
資料存在三種狀態:已定義(Defined,初始化未使用)、已使用(Used)、已終結(Killed,如指標被 free)。
應懷疑的異常資料狀態組合
- 定義-定義——連續定義兩次,浪費或錯誤
- 定義-離開——區域變數定義後未使用就離開常式
- 定義-終結——定義後直接終結,多餘變數或遺漏使用
- 進入-終結 / 進入-使用——區域變數未定義就被終結或使用
- 終結-終結——雙重終結,對指標尤其致命
- 終結-使用——使用已終結的變數是邏輯錯誤
關鍵是測試所有定義-使用路徑。好的策略:先做基礎測試,再補遺漏的定義-使用組合。
等價分割(Equivalence Partitioning)#
將輸入空間分成等價類別,每類只需一個代表值。在基礎路徑與資料流測試後額外洞見有限,但從外部規格出發或面對複雜資料時特別有用。
錯誤猜測與邊界分析#
錯誤猜測(Error Guessing)根據直覺與過往經驗設計測試。邊界分析(Boundary Analysis)針對差一錯誤,每個範圍條件測試三個案例:邊界以下、邊界上、邊界以上。複合邊界則測試多變數同時取極端值的情境。
不良資料與良好資料的類別
不良資料:過少/無資料、過多、錯誤類型、錯誤大小、未初始化。
良好資料:一般案例(中間值)、最小正常組態(如空白試算表)、最大正常組態(產品標示上限)、舊資料相容性(回歸測試基礎)。
選擇容易手算的整數值(如 $20,000)作為測試資料,避免醜數字(如 $90,783.82)。同一等價類別中整數值不會少發現錯誤,但能降低手算出錯的風險。
22.4 典型錯誤#
缺陷分布極度不均:80% 的錯誤集中在 20% 的類別中,50% 集中在 5% 的類別中。IBM IMS 系統修復 425 個類別中的 31 個易出錯類別後,客戶缺陷減少十倍、維護成本降 45%。
維護策略的啟示:應集中識別、重新設計並重寫易出錯的常式,而非在原程式碼上反覆修補。
錯誤特徵:85% 只需修改一個常式;95% 是程式設計師的錯;36% 的建構錯誤是書寫失誤(三個史上最昂貴的軟體錯誤都是修改了單一字元);16-19% 源於誤解設計;85% 在數小時內可修。
建構缺陷佔比:小型專案約 75%,大型專案至少 35%(有些報告高達 75%)。預期缺陷數:業界平均 1-25 個/千行,Microsoft 發布產品 0.5 個/千行,Cleanroom 發布產品 0.1 個/千行,TSP 約 0.06 個/千行。
測試案例的錯誤密度常等於甚至高於被測程式碼。應以開發正式程式碼的態度來開發測試案例:逐行檢查、盡早規劃、保存以供回歸測試、整合到測試框架中。
22.5 測試支援工具#
| 工具 | 說明 |
|---|---|
| 測試鷹架(Scaffolding) | 模擬物件/樁常式(mock/stub)替代依賴項;驅動程式(driver/test harness)呼叫被測物件;假檔案(dummy file)提供已知無誤的小型測試檔案。可用 JUnit、CppUnit、NUnit 等框架 |
| 差異比較工具(Diff) | 比較新輸出與預期輸出,最簡單的回歸測試方法 |
| 測試資料產生器 | 隨機產生異常組合,可加權以強調常見輸入範圍 |
| 覆蓋率監控器 | 未量測覆蓋率的測試通常只執行 50-60% 程式碼 |
| 資料記錄器與日誌 | 記錄系統狀態,類似飛機黑盒子 |
| 符號式除錯器 | 逐行步進觀察變數值,也適合學習語言與編譯器最佳化 |
| 系統擾動器 | 記憶體填充(暴露未初始化變數)、記憶體搖晃(偵測絕對位址依賴)、選擇性記憶體失敗、邊界檢查 |
| 錯誤資料庫 | 追蹤重複錯誤、偵測/修正速率、狀態與嚴重程度 |
22.6 改善測試程序#
規劃測試——從專案初期就將測試提升到與設計和編碼同等的重要性;有計畫才能可重複,可重複才能改善。
回歸測試——修改後重新執行先前的測試以確保未引入新缺陷,舊測試必須保留。
自動化測試——回歸測試的唯一實際做法。手動測試的錯誤率與被測程式碼的缺陷率相當,約只有一半被正確執行。自動化的好處:更不易出錯、可反覆使用、支援頻繁測試實踐、盡早偵測問題、為大規模修改提供安全網。
22.7 保留測試記錄#
缺陷記錄應包含的項目
- 管理資訊(回報日期、回報者、標題、建置編號、修正日期)
- 完整問題描述與重現步驟
- 建議的暫時解法與相關缺陷
- 嚴重程度(致命、困擾、外觀)
- 來源(需求、設計、編碼、測試)與編碼子分類
- 受影響的類別/常式、程式碼行數
- 發現與修正所需時間
可計算的指標:各類別的缺陷數、每缺陷平均測試時間、每測試案例平均缺陷數、覆蓋率、各嚴重等級的未結缺陷數。個人也可維護常見錯誤清單與時間記錄。
更多資源#
- Kaner, Falk, Nguyen, Testing Computer Software, 2nd ed. (1999)——軟體測試全面指南
- Whittaker, How to Break Software (2002)——23 種攻擊手法讓軟體失敗
- Myers, The Art of Software Testing (1979)——經典著作
- Beck, Test-Driven Development: By Example (2003)——先寫測試的完整範例
要點#
- 開發者測試是完整測試策略的關鍵環節,但獨立測試同樣重要
- 先寫測試案例能縮短缺陷偵測-除錯-修正的循環,且不增加額外時間
- 測試只是品質計畫的一部分;高品質的開發方法和協同開發實踐至少同等重要
- 可透過基礎路徑測試、資料流分析、邊界分析、不良/良好資料類別系統性地產生測試案例,再用錯誤猜測補充
- 錯誤集中在少數易出錯的類別與常式——找出它們、重新設計、重新撰寫
- 測試資料的錯誤密度常高於被測程式碼,應以同等嚴謹態度開發測試案例
- 自動化測試對回歸測試不可或缺
- 長期改善測試的最佳方式是使流程規律化、量測並據此調整