哪些應該用單元測試驗證,哪些該留給整合測試 - 切界線的方法與實務判斷表

· · 測試, 單元測試, 整合測試, 測試設計, Windows 開發, C# / .NET

測試設計的話題裡,每次都讓人暗自頭痛的,是要把多少東西塞進單元測試、從哪裡開始往整合測試搬。

這裡的兩個危險做法是:

  • 想快就通通用單元測試
  • 想接近真實就通通用整合測試

前者 mock 過多,會錯過正式環境才會炸的點;後者則讓測試又慢又易壞。
實務上要看的軸其實滿清楚:

  • 想驗的是自家的邏輯,還是跟外部的串接?
  • 換成 in-memory fake,意義會不會掉下來?
  • DB / 檔案 / HTTP / DI / 設定 / 框架 / OS 的行為本身是不是重點?
  • 要不要高速跑大量輸入組合?

這 4 題看清楚,單元測試與整合測試的界線就好畫多了。

本文以 2026 年 3 月時點可查閱的 Microsoft Learn 與 Martin Fowler 的公開資訊為前提,從實務角度整理兩者的界線。123

1. 先下結論

以粗但好用的說法:

  1. 純邏輯 → 單元測試
  2. 連接、接線、轉換、環境差異 → 整合測試
  3. 兩邊都能驗時,先選單元測試
  4. 整合測試不要做得又廣又重,要把邊界收窄

一句話:單元測試是「判斷的測試」,整合測試是「連接的測試」

像金額計算、狀態轉移、輸入驗證、審核條件、例外分類這些「沒有外部資源也能有意義」的東西,放進單元測試,跑得快、壞得少、輸入組合也能鋪得厚。
而像 SQL 執行、JSON / CSV 序列化、路由、model binding、DI 註冊、檔案鎖、權限、COM 註冊、32bit / 64bit、STA / MTA 這些「一接上就可能背叛你的東西」,則應該放到整合測試。

Microsoft Learn 的 Integration tests in ASP.NET Core 也寫得很直白:整合測試要聚焦在關鍵基礎設施情境,能用單元測試處理的就用單元測試。

2. 本文所說的單元測試與整合測試

整理如下:

層級 要驗什麼 典型配置
單元測試 被隔離的單一職責是否正確 用 fake / mock / stub,切斷外部資源
整合測試 多個元件的連接,以及含基礎設施、框架的實際行為 實際 DB、實際檔案、實際 serializer、實際 host、實際 pipeline 等
E2E/功能測試 整個應用的使用者流程 部署後的應用、多個服務、實際瀏覽器或實際行程

.NET 的單元測試說明把好的單元測試描述為 fast / isolated / repeatable,不應依賴檔案系統、資料庫這些外部因素。詳情可看 Unit testing best practices for .NET

而整合測試並不是「一定要跨行程或跨機器的重型測試」。
就算在同一個行程內,只要 把多個實體元件串起來,並驗證框架或基礎設施的真實行為,那就偏整合測試

例如在 ASP.NET Core 上對 controller action 做單元測試時,應該把對象收斂在 action 本體的判斷;routingmodel bindingfilters 這類與框架的互動放到整合測試 — 官方也這樣整理。詳情可參考 Unit test controller logic in ASP.NET Core

3. 一張判斷表

先放最實務好用的表:

要驗什麼 主力測試 補充
金額計算、折扣、狀態轉移、輸入驗證 單元測試 輸入組合要鋪得厚
例外分類、錯誤訊息挑選、是否 retry 的判斷 單元測試 不需實際 I/O 就能驗完
Repository 的 SQL / ORM 轉換、transaction 整合測試 真實 DB 或 provider 的行為是主題
JSON / XML / CSV 的 serialize / deserialize 整合測試 wire format 的偏差用 fake 難抓
路由、model binding、filter、middleware 整合測試 驗證與框架的串接
WPF / WinForms 的 ViewModel 或 Presenter 的狀態轉移 單元測試 不用起 UI 也有意義
真正的 Binding、Dispatcher、control lifecycle、message loop 整合測試或 UI 測試 主題是框架與執行緒行為
檔案路徑、權限、鎖、共享資料夾、換行、編碼 整合測試 需要 OS 與檔案系統的真實行為
COM 註冊、32bit / 64bit、STA / MTA、DLL 載入 整合測試 主題是環境差異與行程邊界
應用整體啟動、主要用例的通關確認 E2E/冒煙 條數不用多

看的重點是:哪個測試最貼近「正式環境會壞的原因」
用「程式碼放在哪」來決定會走偏,用「想消弭什麼不確定性」來決定比較穩。

4. 單元測試該接下什麼

單元測試適合 剝掉外部後仍有意義的職責

例如:

  • 業務規則
  • 分支
  • 狀態轉移
  • 輸入驗證
  • 錯誤分類
  • 決定是否 retry
  • ViewModel / Presenter 的狀態變化
  • 轉換邏輯本身

特別是 組合越多的,越值得放進單元測試

例如:

  • 有/無優惠券
  • 有/無庫存
  • 首次/回購
  • 管理員/一般使用者
  • 正常值/邊界值/非法值

分支條件越多,整合測試要把它們全跑一遍會越重。
這裡放到單元測試細切比較合理。

另外,單元測試要 把外部因素變得可控

  • 目前時間用注入
  • GUID、亂數要能被替換
  • 不要靠 sleep 等
  • 不碰真實 DB 或真實檔案
  • 不走真實網路

這些守好,測試就會相當穩定。

4.1. 單元測試的 mock 變太多時

想寫單元測試,結果:

  • 要 7 個 mock
  • setup 很長
  • arrange 比本體長
  • 看不出到底在驗什麼

多半就是下列其一:

  1. 那個 class 職責過載
  2. 本該交給整合測試驗的接線,硬塞進單元測試

mock 的用途是把外部切開,不是用來證明「你和真實環境串得對」
搞錯這點,就容易變成「全部 green 但正式環境掛掉」。

5. 該往整合測試的 4 個邊界

該往整合測試走的地方,大致可分 格式、接線、環境、時間 4 類。

5.1. 格式邊界

這裡說的格式包括:

  • JSON / XML / CSV
  • DB 的 schema 與 mapping
  • nullable / precision / timezone
  • enum、日期的序列化
  • 編碼與 BOM
  • 換行

Martin Fowler 也說,有 serialize / deserialize 的邊界都是整合測試的候選。詳細可看 The Practical Test Pyramid

像:

  • 把 DTO 轉 JSON 結果欄位名不對
  • CSV 的引號、換行壞了
  • decimal 被四捨五入掉
  • DB 上 DateTimeOffset 處理偏了
  • null 與空字串被當成一樣的東西

這類 bug 單元測試往往抓不到。

5.2. 接線邊界

接線邊界的例子:

  • DI 註冊
  • 設定 bind
  • 路由
  • model binding
  • filter
  • middleware
  • host 啟動
  • 事件接線
  • WPF 的 Binding 或 command 綁定

這些的重點不在「我的函式對不對」,而是 多個實體元件有沒有被正確接起來

在 ASP.NET Core,官方把 controller action 的單元測試收斂到 action 的判斷本身;routingmodel bindingfilters 留給整合測試。
桌面應用也是同樣思路:ViewModel 的狀態轉移用單元測試,包含實際 XAML Binding、Dispatcher 的行為則偏整合測試。

5.3. 環境邊界

在 Windows 開發裡,這塊特別重要:

  • 檔案權限
  • 共享資料夾
  • 檔案鎖
  • 從暫存檔 rename
  • 系統管理員權限
  • 服務啟動權限
  • COM 註冊
  • 32bit / 64bit
  • STA / MTA
  • DLL 從哪載入

這些都是 OS 或執行環境本身的條件 當主角。
用 in-memory fake 會失真太多,應該交給整合測試。

特別是既有 Windows 軟體或含 COM / ActiveX 的架構,常常不是邏輯出事,而是 註冊、bitness、執行緒模型、權限 先出事。
這類故障不是單元測試的守備範圍,而是要把環境納入的整合測試才撿得到。

5.4. 時間邊界

另一個容易漏的是時間與並行。

  • timeout
  • cancellation
  • retry 的真實行為
  • timer 驅動
  • 背景處理的停止
  • race condition
  • shutdown 時的結束順序

這裡重點是 把「判斷」與「真實行為」分開

例如:

  • 最多 retry 幾次
  • 哪些例外要 retry

用單元測試就夠。
但:

  • timeout 真的有生效嗎
  • cancellation 有傳遞下去嗎
  • timer 和非同步處理撞在一起會不會壞
  • 結束時 handle、task 是否乾淨關閉

就偏整合測試了。

6. 常見判斷錯誤

6.1. 把 Repository 全 mock 了就滿足

把 Repository 相關都 mock 掉,下面這些都驗不到:

  • SQL 對不對
  • transaction 有沒有效
  • 與 schema 是否一致
  • mapping 有沒有歪
  • 編碼或 precision 有沒有壞

Repository 往往不是 邏輯測試的對象,而是 邊界的接點
這種情況應把整合測試的比重拉高,比單元測試更貼近實況。

6.2. 在 Controller / Endpoint 的單元測試想連框架一起驗

controller action 的單元測試該驗的是:

  • 條件分支
  • 回傳值的選擇
  • 相依服務的呼叫方式

而:

  • route 對不對得上
  • model binding 能不能通
  • filter 生效了嗎
  • 過完 middleware 後變怎樣

是整合測試的事。
混在一起就分不清哪裡壞掉。

6.3. 用整合測試窮舉輸入組合

整合測試接近真實,所以一定慢。
所以 分支窮舉給單元測試,邊界代表情境給整合測試 比較划算。

Microsoft Learn 的整合測試說明也推薦:對 DB 或檔案系統不要用整合測試跑所有組合,而是 收斂到 read / write / update / delete 這類代表性情境

6.4. 在 CI 直接打外部服務的正式環境

這應該避免。

整合測試強調「接近真實」,但不代表要天天打真正的 SaaS 或正式 API。
Fowler 也建議把外部服務搬到本地、用 fake,或用專用的 test instance。

實務上常用的組合是:

  • 本地 DB
  • 暫存資料夾
  • test host
  • 專屬的 test environment
  • 以契約為準的 fake service

7. 實務上的建議架構

比例沒有絕對答案,但下面這 3 層非常通用:

主力 放什麼
核心層 單元測試做厚 業務規則、狀態轉移、輸入驗證、錯誤分類
邊界層 窄而精的整合測試 DB、檔案、HTTP、serializer、DI、設定、COM、權限
整體層 少量的冒煙 / E2E 啟動確認、主要流程、重大故障的防再發

感覺上,用數量撐起來的是單元測試,用邊界濃度撐起來的是整合測試

推薦做法:

  1. 先盤點應用的邊界
  2. 把邏輯重整成「可和外部切開」的形狀
  3. 每個邊界至少放 1 條 happy path + 1 條代表性 failure path
  4. 整體通關的條數要收斂
  5. 出 bug 時,把測試補在 能用最低成本重現那個 bug 的那一層

第 5 點很關鍵:

  • 規則寫錯:加單元測試
  • SQL/binding/設定/權限/註冊錯:加整合測試
  • 包含啟動或部署的故障:加冒煙或 E2E

這樣擴張,測試的職責不會走樣。

8. 猶豫時最後問的 5 個問題

猶豫時把這 5 題走一遍:

  1. 換成 in-memory fake,要驗的意義還在嗎
    • 還在 → 偏單元測試
  2. 壞掉時首先懷疑的是邏輯,還是連接、設定
    • 連接或設定 → 偏整合測試
  3. 主題是 DB/檔案/serializer/DI/route/model binding/OS/權限/bitness/thread 嗎
    • 是 → 偏整合測試
  4. 要不要高速跑大量輸入組合
    • 要 → 偏單元測試
  5. 這個測試壞了,能馬上知道該改哪裡嗎
    • 不能 → 你的測試層級混在一起了

用這 5 題,就比較不會用「感覺比較接近真實,放整合測試」「感覺比較快,放單元測試」這種粗放決策。

9. 總結

單元測試與整合測試的界線,不該用「程式碼放哪」來切,而該用 要消弭什麼不確定性 來切,這才最實務。

重點整理:

  • 單元測試是判斷的測試
  • 整合測試是連接的測試
  • 分支窮舉走單元測試
  • 格式、接線、環境、時間走整合測試
  • 整體通關用少量的冒煙 / E2E 押住

最該避免的是:

  • 以為 mock 也能證明「和真實的連接是對的」
  • 整合測試去跑所有分支
  • 把單元測試與整合測試的職責混在一起

猶豫時先問:這個 bug 是「判斷」壞,還是「連接」壞?
光這一題,多數情境就能劃出清楚界線。

10. 相關文章

11. 參考資料

共用相同標籤的最新文章。能以相近的主題延伸理解。

與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。

本文連結到以下服務頁面,歡迎從最接近的入口查看。

作者檔案

本文作者的個人檔案頁面。

Go Komura

小村軟體有限公司 代表

以 Windows 軟體開發、技術諮詢與故障調查為中心,在難以重現的故障調查與既有資產仍在運作的專案上具有優勢。

回到部落格一覽