測試類型全解
是什麼?(測試類型為何要分這麼多種)
身為資深 C# 開發者,你一定寫過 xUnit 的 [Fact],也跑過整個系統的端對端冒煙。這些「測試」表面上都是在驗證程式,但它們回答的問題、執行的時機、由誰負責、跑起來多快多貴,差異極大。
把測試分類的核心理由,是讓團隊能在不同抽象層級回答不同問題:
- 這個方法的邏輯對不對?(單元)
- 兩個模組接在一起會不會出事?(整合)
- 整個系統符合規格書嗎?(系統)
- 使用者真正想要的東西做出來了嗎?(UAT)
分類不是學術潔癖,而是讓你能有意識地分配測試投資:哪些要多寫、哪些要少寫、哪些要自動化、哪些必須人工。這份課程就是測試類型的「地圖」。
ℹ️本課定位
這課聚焦在「有哪些測試類型、各是什麼、怎麼比較、何時用」。至於「如何制定測試策略、產出測試報告與覆蓋率指標」,請見站上的測試策略與報告課程,兩者互補。
兩大分類:功能性 vs 非功能性測試
最頂層的分類,是問測試在驗證「行為」還是「品質屬性」。
| 維度 | 功能性測試 | 非功能性測試 |
|---|---|---|
| 回答的問題 | 系統「做了什麼」對不對 | 系統「做得好不好」 |
| 依據 | 功能規格、需求 | 品質屬性、SLA、NFR |
| 例子 | 登入成功會導向首頁 | 一萬人同時登入仍 1 秒內回應 |
| 典型類型 | 單元、整合、系統、E2E、UAT | 效能、安全、可用性、相容性 |
功能性測試問「對不對」,非功能性測試問「夠不夠好」。兩者都會失敗讓使用者離開,只是離開的理由不同。
功能性測試逐一詳解
下面每一種都從四個角度說明:定義、測什麼、誰做、何時做。
單元 Unit
驗證單一方法或類別的邏輯,隔離外部相依
整合 Integration
驗證多個模組或外部資源(DB、API)接起來能運作
系統 System
以完整系統為對象,驗證是否符合規格
端對端 E2E
模擬真實使用者,從 UI 走完整流程
驗收 UAT
由真實使用者驗收是不是他要的
單元測試(Unit Test)
- 定義:針對最小可測單位(通常是一個方法或類別)進行隔離驗證,外部相依以 mock/stub 取代。
- 測什麼:純邏輯、分支、邊界條件、例外處理。
- 誰做:開發者本人,通常與生產程式碼一起寫(TDD 更是先寫測試)。
- 何時做:每次 commit、每次 build,秒級回饋。
整合測試(Integration Test)
- 定義:驗證多個元件「組合在一起」是否正確協作,常會碰到真實的資料庫、訊息佇列或外部 API。
- 測什麼:模組邊界、資料庫對應、序列化、交易、第三方串接。
- 誰做:開發者為主,有時與 QA 協作。
- 何時做:CI 階段,比單元測試慢(牽涉 I/O)。
系統測試(System Test)
- 定義:把整個系統當成黑箱,驗證它整體是否符合規格書。
- 測什麼:完整功能流程、跨模組情境、與規格的一致性。
- 誰做:QA/測試團隊。
- 何時做:功能開發完成、進入測試環境後。
端對端測試(E2E Test)
- 定義:從使用者實際入口(瀏覽器、App)出發,走完一條完整業務流程。
- 測什麼:真實環境下的關鍵使用者旅程,例如「下單到付款成功」。
- 誰做:QA 或開發者,常用 Playwright、Selenium 自動化。
- 何時做:發布前的關鍵路徑驗證,數量應精選。
驗收測試/UAT(User Acceptance Test)
- 定義:由真實使用者或業務代表驗證系統「是不是他們真正要的」。
- 測什麼:商業需求、實際工作流程的可用性。
- 誰做:終端使用者、產品負責人、業務單位。
- 何時做:上線前最後一關。(下方有專段詳解。)
冒煙測試(Smoke Test)
- 定義:建置完成後的「能不能開機」檢查,確認最關鍵功能可運作,值不值得繼續測。
- 測什麼:少數核心路徑,例如服務能啟動、首頁能開、登入能過。
- 誰做:QA 或自動化流水線。
- 何時做:每次新版本部署到測試環境後第一件事。
健全測試(Sanity Test)
- 定義:針對某個修改或修復,做一次窄而淺的快速驗證,確認該特定功能合理運作。
- 測什麼:剛改動的小範圍功能。
- 誰做:QA。
- 何時做:收到小型修補後,決定要不要進行完整回歸。
回歸測試(Regression Test)
- 定義:在程式碼變動後,重新執行既有測試,確認原本好的功能沒被改壞。
- 測什麼:既有功能集合,理想上盡量自動化。
- 誰做:自動化流水線為主,QA 補充。
- 何時做:每次有變更(修 bug、加功能、改設定)之後。
💡冒煙 vs 健全 vs 回歸
冒煙是「廣而淺」決定要不要測下去;健全是「窄而深」驗剛改的點;回歸是「廣而深」確認沒退步。三者目的不同,別混為一談。
非功能性測試
功能對了,不代表系統能用。非功能性測試驗證的是品質屬性(NFR)。
| 類型 | 驗證什麼 | 典型問題 |
|---|---|---|
| 效能 Performance | 回應時間、吞吐量 | 在預期負載下夠快嗎 |
| 負載 Load | 預期尖峰下的表現 | 雙十一流量撐得住嗎 |
| 壓力 Stress | 超過極限後的行為 | 崩潰時是否優雅降級而非雪崩 |
| 安全 Security | 漏洞與權限 | 能否被 SQL Injection 或越權存取 |
| 可用性 Usability | 操作直覺度 | 使用者找得到按鈕嗎 |
| 可存取性 Accessibility | 無障礙支援 | 螢幕報讀器、鍵盤可操作嗎 |
| 相容性 Compatibility | 跨環境一致 | 各瀏覽器、OS、裝置都正常嗎 |
| 可靠性 Reliability | 長時間穩定 | 連續運行三天會記憶體洩漏嗎 |
ℹ️負載與壓力的差別
負載測試問「在我預期的最大流量下還順嗎」;壓力測試刻意把流量推過天花板,看系統「壞掉時壞得好不好看」(是否優雅降級、能否自動恢復)。
測試金字塔與重要性權衡
測試金字塔(Test Pyramid)告訴你各層級該寫多少量。
為什麼底層多、上層少?因為越往下:
- 越快:單元測試毫秒級,E2E 動輒數十秒。
- 越穩:單元測試很少 flaky;E2E 受網路、時序、環境影響容易隨機失敗。
- 越便宜:定位失敗成本低,一個單元測試紅了就知道哪壞了。
冰淇淋甜筒反模式(Ice-cream Cone) 是金字塔倒過來:大量手動與 E2E 測試、極少單元測試。結果是測試又慢又脆、回饋遲緩、維護成本爆炸。很多團隊在「補測試」時不小心就堆成了甜筒。
🤔 團隊發現 CI 跑得很慢且測試常常隨機失敗(flaky),追查後發現大量測試都是透過瀏覽器走完整流程,單元測試卻寥寥無幾。這是什麼反模式?
各類型一覽比較
| 類型 | 層級 | 速度 | 成本 | 誰負責 | 自動化程度 | 何時跑 |
|---|---|---|---|---|---|---|
| 單元 | 最低 | 極快 | 低 | 開發者 | 全自動 | 每次 build |
| 整合 | 低 | 中 | 中 | 開發/QA | 全自動 | CI |
| 系統 | 高 | 慢 | 中高 | QA | 半至全自動 | 測試環境就緒後 |
| E2E | 最高 | 很慢 | 高 | QA/開發 | 自動(易脆) | 發布前 |
| 冒煙 | 跨層 | 快 | 低 | QA/CI | 自動 | 每次部署後第一步 |
| 健全 | 視範圍 | 快 | 低 | QA | 多為手動 | 收到小修補後 |
| 回歸 | 跨層 | 中至慢 | 中 | CI 為主 | 盡量自動 | 每次變更後 |
| UAT | 業務 | 慢 | 高 | 使用者/PO | 多為手動 | 上線前 |
UAT 專段
UAT=User Acceptance Testing,使用者驗收測試,是上線前由真實使用者或業務代表執行的最後一道關卡。
和系統測試的關鍵差別(面試常考):
- 系統測試問「做得對不對」——系統行為是否符合規格書,由 QA 以技術視角驗證。
- UAT 問「是不是真正要的」——系統是否滿足實際商業需求,由使用者以業務視角驗證。
規格可能本身就寫錯,所以系統測試全綠、UAT 卻不通過,是完全可能而且很常見的情況。
Alpha 與 Beta:
- Alpha 測試:在開發方內部、受控環境,由內部人員(非開發團隊本身)模擬真實使用。
- Beta 測試:把產品交給真實外部使用者在真實環境試用,蒐集回饋(即「公測」)。
誰來測、驗收標準: UAT 由終端使用者、產品負責人或業務單位執行,依事先約定的驗收標準(Acceptance Criteria) 逐條確認。標準通常以業務語言寫成,例如「主管能在一個畫面核准請假並收到通知」。
🤔 系統測試所有案例都通過了,但 UAT 階段業務說「這不是我要的東西」。最合理的解釋是?
C# 範例(xUnit)
下面用同一個情境,對照單元測試與整合測試的差別。注意單元測試把相依隔離掉,整合測試則真的碰外部資源。
// 受測對象:下單服務
public class OrderService
{
private readonly IInventoryRepository _inventory;
public OrderService(IInventoryRepository inventory) => _inventory = inventory;
public bool TryPlaceOrder(string sku, int qty)
{
if (qty <= 0) return false;
var stock = _inventory.GetStock(sku);
return stock >= qty;
}
}// 單元測試:用 mock 隔離資料庫,只驗 OrderService 的邏輯
public class OrderServiceUnitTests
{
[Theory]
[InlineData(5, 3, true)] // 庫存足夠
[InlineData(5, 10, false)] // 庫存不足
[InlineData(5, 0, false)] // 數量非法
public void TryPlaceOrder_ChecksStockCorrectly(int stock, int qty, bool expected)
{
var repo = new Mock<IInventoryRepository>();
repo.Setup(r => r.GetStock("SKU-1")).Returns(stock);
var sut = new OrderService(repo.Object);
var result = sut.TryPlaceOrder("SKU-1", qty);
Assert.Equal(expected, result);
}
}// 整合測試:接真實 DB(測試容器),驗 Repository 與資料庫的協作
public class InventoryRepositoryIntegrationTests : IClassFixture<SqlServerFixture>
{
private readonly string _connectionString;
public InventoryRepositoryIntegrationTests(SqlServerFixture fx)
=> _connectionString = fx.ConnectionString;
[Fact]
public void GetStock_ReadsSeededRowFromDatabase()
{
// 前置:資料庫已 seed 一筆 SKU-1 庫存 7
var repo = new InventoryRepository(_connectionString);
var stock = repo.GetStock("SKU-1");
Assert.Equal(7, stock); // 真的查到資料庫的值
}
}重點對照:單元測試毫秒級、無外部相依、可大量寫;整合測試較慢、需真實 DB、驗的是接線正確。
常見誤區
⚠️這些坑很多資深團隊也會踩
- 只寫單元測試、沒有整合測試:每個零件單獨都對,組起來卻在交易邊界、序列化或連線設定上爆炸。單元全綠不代表系統能跑。
- E2E 過多導致脆弱:把驗證重擔壓在 UI 層,CI 變慢、測試 flaky,最後團隊乾脆把紅燈當噪音忽略——比沒測試還危險。
- 把 UAT 當成 QA 的事:UAT 的主角是「使用者/業務」,不是測試工程師。讓 QA 代替使用者驗收,等於沒驗收,上線後才發現「做的不是要的」。
- 混淆系統測試與 UAT:以為系統測試過了就等於使用者會滿意,忽略了規格本身可能就錯。
實戰補充(Q&A)
Q:回歸測試怎麼自動化? 把過去出過 bug 的案例與關鍵路徑沉澱成自動化測試套件,掛進 CI,每次 commit/PR 觸發。策略上以單元與整合測試承擔大部分回歸(快又穩),E2E 只覆蓋最關鍵旅程。修了一個 bug,就補一條對應的回歸測試,避免同一個洞再掉進去。
Q:該寫多少 E2E? 依金字塔精神,E2E 應精選而非全面:只覆蓋「壞了就會出大事」的關鍵業務流程(登入、結帳、付款)。其餘邏輯下推到單元與整合層去驗。一個常見的健康訊號是:當你想用 E2E 去測一個分支邏輯時,多半代表那段邏輯應該被往下挪到單元測試。
理解測驗
🤔 哪一組描述正確對應了「冒煙測試」的定位?
🤔 關於測試金字塔,下列何者最正確?
重點整理
💡一句話記住
單元驗邏輯、整合驗接線、系統驗規格、UAT 驗需求;金字塔底大頂小,回歸保你不退步。系統測試問「做得對不對」,UAT 問「是不是真正要的」。
| 你想回答的問題 | 該用的測試 |
|---|---|
| 這個方法邏輯對嗎 | 單元 |
| 模組組起來能運作嗎 | 整合 |
| 整個系統符合規格嗎 | 系統 |
| 真實流程從頭到尾通嗎 | E2E |
| 這版能不能繼續測 | 冒煙 |
| 剛改的點合理嗎 | 健全 |
| 原本好的有沒有被改壞 | 回歸 |
| 是不是使用者真正要的 | UAT |
| 夠不夠快、安不安全、撐不撐得住 | 非功能性(效能/安全/負載等) |