測試策略與測試報告
是什麼?
測試策略(Test Strategy)回答的是「用有限的時間與成本,怎麼把測試火力分配到最該測的地方」。它不是「把所有東西都測一遍」,而是一份取捨計畫:哪些用快又便宜的單元測試守、哪些必須串起來測、哪些只能靠少量端到端測試把關,以及效能、安全這些非功能面要不要測、測到什麼程度。
對資深 C# 開發者而言,重點不在「會不會寫測試」,而在能不能說清楚這個系統的測試取捨與風險。本課直接回答四個讀者最常問的問題:要測哪些、測試報告怎麼寫、測試如何傳承、以及「寫完是不是就不用看了」。
ℹ️測試策略 vs 測試計畫 vs 測試案例
**策略(Strategy)**是組織層級的長期方針(我們重視哪幾類測試、覆蓋率門檻多少);**計畫(Plan)**是某個版本/專案的具體安排(這次測什麼、誰測、環境為何);**案例(Test Case)**才是一條一條的具體驗證步驟。面試常混為一談,分清楚會加分。
要測哪些
測試金字塔(Test Pyramid)描述的是數量比例:越底層越多、越快、越便宜;越上層越少、越慢、越貴。倒過來變成「冰淇淋甜筒」(E2E 一大堆、單元測試很少)是反模式 —— 跑得慢又脆弱。
除了金字塔上的功能性測試,還有一整類常被忽略的非功能性測試(NFR),這正是資深工程師與資淺工程師的分水嶺:
| 類別 | 要驗證什麼 | 代表工具/手法 |
|---|---|---|
| 單元測試 | 單一類別/方法的邏輯正確 | xUnit、NUnit、Moq |
| 整合測試 | 多個元件/DB/外部服務串接正確 | WebApplicationFactory、Testcontainers |
| E2E 測試 | 使用者完整流程可走通 | Playwright、Selenium |
| 效能/負載 | 回應時間、吞吐量、壓力下是否崩潰 | k6、JMeter、BenchmarkDotNet |
| 安全 | 注入、認證授權、機敏資料外洩 | OWASP ZAP、靜態掃描 SAST |
| 可用性/可存取性 | 操作流暢度、無障礙(WCAG) | axe、人工走查 |
| 相容性 | 跨瀏覽器、跨 OS、跨版本 | BrowserStack、矩陣測試 |
💡非功能測試不是「有空再做」
效能與安全問題通常在正式環境才爆發,且修復成本極高。把基本的負載基準與安全掃描放進 CI,比上線後救火便宜一個數量級。
各層測試該驗證什麼
每一層有不同的「職責邊界」,搞混就會寫出又慢又難維護的測試:
| 層級 | 目標 | 範圍 | 速度 | 數量比例 |
|---|---|---|---|---|
| 單元 | 邏輯分支正確 | 單一類別,外部依賴全部 mock | 毫秒級 | ~70% |
| 整合 | 元件間契約正確 | 真實 DB/HTTP,少量 mock | 秒級 | ~20% |
| E2E | 業務流程可交付 | 整個系統黑箱 | 數十秒~分鐘 | ~10% |
判斷原則:能在單元層測的,就不要拉到整合層測(慢且脆);反過來,牽涉到 SQL 語法、序列化、跨服務契約的,單元測試 mock 不出真相,該往整合層走。E2E 只留給「最賺錢、出錯最痛」的關鍵動線,例如登入、下單、付款。
測試報告怎麼寫
測試報告是給決策者看的(PM、QA Lead、發布負責人),核心問題只有一個:這個版本能不能發布? 一份能被信任的報告通常包含這個骨架:
測試範圍
這次測了哪些功能與非功能項,哪些明確不在範圍內
執行環境
版本號、OS、瀏覽器、資料庫、測試資料來源
執行結果
通過/失敗/略過 數量,以及覆蓋率數字
缺陷清單
每個缺陷的描述、重現步驟與嚴重度分級
已知風險
未測到的區域、暫時繞過的問題、技術債
結論與建議
是否建議發布,附帶條件或回滾計畫
缺陷一定要分嚴重度(Severity),不要只丟一張「有 12 個 bug」的清單:
| 嚴重度 | 定義 | 範例 |
|---|---|---|
| Blocker | 阻斷主流程,無法繼續測 | 登入直接崩潰 |
| Critical | 核心功能錯誤、資料損毀 | 下單金額計算錯 |
| Major | 重要功能異常但有 workaround | 匯出報表格式跑掉 |
| Minor | 體驗瑕疵 | 按鈕對齊歪一點 |
⚠️覆蓋率數字會騙人
報告寫「覆蓋率 85%」沒有意義,要回答「覆蓋了什麼」。85% 涵蓋核心金流,跟 85% 全在 getter/setter 上,是天差地遠的兩件事。報告要附「核心模組各自的覆蓋率」,而不只是一個總數。
測試如何傳承
測試最被低估的價值,是它能成為活文件(Living Documentation):永遠跟程式碼同步、不會像 Word 規格書那樣過期。三年後接手的人,讀測試就能讀懂需求。
- 測試命名即規格:方法名要描述「在什麼情境下、做了什麼、預期什麼結果」,而不是
Test1、TestAddItem。 - BDD/Gherkin 規格:用
Given / When / Then把驗收條件寫成「業務也讀得懂」的語言(SpecFlow / Reqnroll),需求與測試合而為一。 - 覆蓋率報告進 CI:每次提交自動產生覆蓋率與趨勢,讓「品質可見」。
- 用測試表達意圖:當你想說明「這個邊界條件為什麼要這樣處理」,與其寫註解,不如寫一個命名清楚的測試 —— 註解會過期,會失敗的測試不會。
Feature: 購物車金額計算
Scenario: 滿千折百
Given 購物車內商品總額為 1200 元
When 結帳
Then 應折抵 100 元,應付金額為 1100 元💡判斷活文件好不好的標準
讓一個沒參與開發的人只讀測試名稱(不讀實作),能不能講出這個模組大致在做什麼、有哪些規則?做得到,這套測試就是合格的活文件。
「寫完就不用看了嗎?」
不是。測試是活的資產,不維護就會腐爛、甚至反過來騙你。
回歸測試
每次改動都重跑既有測試,確保沒弄壞舊功能
CI 自動執行
每次 push / PR 自動跑全套,紅燈不准合併
覆蓋率守門
設門檻,新程式覆蓋率掉破線就擋下
維護 flaky test
時好時壞的測試要立刻修或隔離,別讓人習慣性忽略紅燈
淘汰過時測試
需求變了,對應測試要更新或刪除,不留殭屍測試
⚠️flaky test 是測試體系的癌症
一個時不時亂失敗的測試,會訓練整個團隊「看到紅燈先重跑一次」。久了真正的 bug 也被當成 flaky 忽略掉,整套 CI 的信任度歸零。發現 flaky test 要當成 P1 處理:修好、隔離(quarantine)或刪掉,三選一,不要放著。
C# 範例
一個好的測試,光看名字就知道它在驗什麼;內部則用 Arrange / Act / Assert 三段式讓意圖一目了然。
using Xunit;
public class ShoppingCartTests
{
// 命名即規格:情境_動作_預期結果
[Fact]
public void Checkout_當總額滿千_應折抵一百元()
{
// Arrange:準備情境
var cart = new ShoppingCart();
cart.AddItem(new Item("書", price: 1200m));
// Act:執行被測動作
var payable = cart.Checkout();
// Assert:驗證單一明確結果
Assert.Equal(1100m, payable);
}
[Theory]
[InlineData(999, 999)] // 未達門檻不折
[InlineData(1000, 900)] // 剛好達門檻
public void Checkout_依總額是否滿千_決定是否折抵(decimal total, decimal expected)
{
var cart = new ShoppingCart();
cart.AddItem(new Item("X", total));
Assert.Equal(expected, cart.Checkout());
}
}重點:測試名稱用中文或清楚的英文描述「情境+預期」,後人不用讀實作就懂規則;[Theory] + InlineData 把多個邊界條件變成可讀的規格表。
常見誤區
⚠️這三個坑資深工程師也常踩
- 只看通過率、不看覆蓋什麼:「全部綠燈」可能只代表你測的都是不會錯的地方,真正的風險區一條測試都沒有。
- 盲目追求 100% 覆蓋率:最後 5% 的成本往往超過前 80%,且容易逼人寫出沒有斷言、純粹「跑過」的假測試。覆蓋率是手段不是目的。
- 測試與實作耦合過深:測試斷言「呼叫了哪個私有方法幾次」而非「行為結果」,導致每次重構都得連測試一起改 —— 測試從資產變成負債。應該測「行為(黑箱)」,不要測「實作細節」。
實戰補充(Q&A)
Q:測試金字塔一定要遵守 70/20/10 嗎? A:比例是經驗法則不是聖旨。重點是「底層多、頂層少」的形狀。某些以整合為核心的系統(例如薄商業邏輯、重資料庫的服務)整合層會更厚,這合理;但 E2E 永遠該是最少的。
Q:沒時間寫測試怎麼辦? A:用風險排序。先替「改最頻繁 × 出錯最痛」的模組(金流、權限)補測試,其餘暫緩。沒有資源全測時,測試策略的價值正是幫你決定「不測什麼」。
Q:測試報告要多詳細? A:看讀者。給工程師的可附失敗堆疊與 log;給發布決策者的,一頁講清楚「通過率、關鍵缺陷、可否發布」即可。同一份原始數據,產出不同摘要。
Q:覆蓋率守門設多少合理? A:與其設一個高總門檻,不如設「新增/異動程式不得低於 X%」(diff coverage)。這樣不會被舊程式拖累,又能保證新債不增加。
理解測驗
🤔 關於測試金字塔,下列何者正確?
🤔 一份測試報告寫著「覆蓋率 90%、全部通過」,為什麼資深工程師仍不該直接判定可發布?
🤔 關於「測試即活文件」,下列哪個做法最能讓測試傳承給後人?
重點整理
💡一句話記住
測試策略是「把火力分配到最該測的地方」的取捨計畫;測試是會隨需求一起活著、需要持續維護的文件,而不是寫完就封存的證明。
| 讀者的問題 | 一句話答案 |
|---|---|
| 要測哪些? | 依金字塔分配單元/整合/E2E,再加上效能、安全等非功能測試 |
| 測試報告怎麼寫? | 範圍→環境→結果與覆蓋率→分級缺陷→已知風險→可否發布 |
| 如何傳承? | 清楚命名+BDD 規格+覆蓋率進 CI,讓測試成為活文件 |
| 寫完就不用看了嗎? | 不。CI 持續回歸、覆蓋率守門、修 flaky、淘汰過時測試 |