API 測試把「使用者可以依賴什麼」轉成具體案例,再比較實際觀察與契約承諾。 呼叫完成、沒有拋出例外,或只收到成功訊號,都不足以判斷行為正確; 還要確認結果的意義,以及操作後該改變與不該改變的狀態。
本文接續〈文件與契約描述〉, 說明如何選擇案例、觀察公開行為,以及理解契約測試與整合測試的範圍。 不預設 HTTP、程式語言或框架,也不要求先建立一個 API 專案。
查證日期:2026-11-02。本文查閱 Python 3.14 unittest、JSON Schema Draft 2020-12,
以及查證當日的 Pact 與 Microsoft ASP.NET Core 10.0 文件;後兩者是持續更新的文件。
本文只查閱文件,未實作或執行 API、測試程式或測試工具;案例與偽碼皆為教學推演。
從契約找出可觀察的檢查
一個測試案例(test case)需要說明:在什麼初始條件下,提供什麼輸入,
進行哪項操作,以及哪些觀察必須成立。
斷言(assertion)則把預期寫成可判定通過或失敗的條件,例如「舊標題等於呼叫前的標題」。
Python 的 unittest 文件以特定輸入的預期反應說明案例,並示範結果相等、條件成立與例外的檢查;
這些概念不限於 Python 的語法。參考 Python 3.14:unittest 基本概念與範例。
沿用〈輸入、輸出與失敗語意〉的自創教學契約:
設定清單標題(title) → 成功(舊標題, 新標題) 或 失敗(標題無效)
本例只處理記憶體內的一份可修改清單,不涉及檔案、網路或其他程式同時修改。
輸入必須提供且為字串,長度是 1 到 20 個 Unicode 碼位,包含兩端;
原樣保留文字,不移除空白、不截短、不做其他轉換,只有空白的有效字串也可接受。
成功時只設定標題,回傳呼叫前的舊值與設定後的新值,不改變項目內容與順序;同標題也成功。
未提供、空值、非字串、長度為 0 或超過 20,均回傳 失敗(標題無效),不改變任何清單資料。
可以把其中幾項承諾拆成觀察:
| 契約條款 | 要檢查什麼 |
|---|---|
| 回傳成功與舊、新標題 | 結果種類正確,兩個值各自等於呼叫前與設定後的標題,順序不能顛倒 |
| 原樣保留輸入 | 結果的新標題與呼叫後的標題都等於完整輸入,包含空白 |
| 成功不改變項目 | 呼叫前後的項目內容與順序相同 |
| 無效輸入不改變資料 | 失敗種類正確,且呼叫前後的全部清單資料相同 |
預期應來自契約,不能只把這次實作回傳的內容抄成正確答案。 例如程式先移除空白,再同時回傳並儲存處理後的值,兩者雖然一致,仍違反本例的原樣保留規則。
正常、邊界與失敗案例
以下每列都獨立從標題「待辦」、項目 [「甲」, 「乙」] 的清單開始。
表中的引號只是展示界線,不屬於輸入;「甲 × 20」代表連續 20 個「甲」,不是這段描述文字。
| 輸入 | 預期結果 | 呼叫後狀態 |
|---|---|---|
| 「讀書」 | 成功(「待辦」, 「讀書」) | 標題為「讀書」,項目內容與順序不變 |
| 「甲」,1 個碼位 | 成功(「待辦」, 「甲」) | 標題為「甲」,項目不變 |
| 甲 × 20 | 成功,舊標題為「待辦」、新標題為這 20 個「甲」 | 標題完整保留 20 個「甲」,項目不變 |
| 空字串,0 個碼位 | 失敗(標題無效) | 全部清單資料不變 |
| 甲 × 21 | 失敗(標題無效) | 全部清單資料不變,不能截短後成功 |
未提供、空值、數字 123,各一個案例 | 各自得到 失敗(標題無效) | 每個案例的全部清單資料都不變 |
| 「 讀書 」 | 成功(「待辦」, 「 讀書 」) | 標題保留前後空白,項目不變 |
| 一個空白,1 個碼位 | 成功,舊標題為「待辦」、新標題為該空白 | 標題保留該空白,項目不變 |
| 「待辦」 | 成功(「待辦」, 「待辦」) | 標題與項目都不變,但結果仍是成功 |
1 與 20 檢查被包含的上下限,0 與 21 檢查緊鄰範圍外的情況。 未提供、空值與空字串的意義不同,不能只挑其中一個,便認為都已驗證。 長度也應按契約的 Unicode 碼位判定;本表用單一「甲」碼位避免展示歧義, 卻沒有涵蓋所有文字組合,不能據此宣稱所有 Unicode 處理都正確。
把案例寫成檢查步驟
下列是未執行的概念偽碼,沒有對應實作;「觀察清單」表示取得公開可讀的狀態,並非新增 API。 「保存副本」表示保留呼叫前的資料值,避免前後兩次觀察其實都指向已被修改的同一份資料。
成功案例:保留空白
準備:清單標題為「待辦」,項目為 [「甲」, 「乙」]
保存副本:呼叫前的項目內容與順序
呼叫:設定清單標題(「 讀書 」)
斷言:結果等於 成功(「待辦」, 「 讀書 」)
斷言:觀察到的標題等於「 讀書 」
斷言:觀察到的項目等於呼叫前保存的副本
失敗案例:超過長度上限
準備:另一份清單,標題為「待辦」,項目為 [「甲」, 「乙」]
保存副本:呼叫前的全部清單資料
呼叫:設定清單標題(連續 21 個「甲」)
斷言:結果等於 失敗(標題無效)
斷言:觀察到的全部清單資料等於呼叫前保存的副本
準備初始狀態與必要清理的安排,常稱為 test fixture。
每個案例應能自行取得所需狀態,不依賴另一個測試先成功改好標題,否則執行順序可能影響結果。
Python unittest 要求案例能獨立或與其他案例組合執行,並提供每個測試的準備與清理機制。
參考 Python 3.14:Organizing test code。
從公開邊界測,不把內部做法當承諾
針對 API 契約的檢查,應透過受支援的入口呼叫,並從使用者可取得的結果與狀態判斷。 本例需要觀察標題與項目,不需要指定私有欄位名稱、內部儲存結構,或某個輔助函式一定被呼叫幾次。 這是依前篇「公開契約與實作分開」所作的測試設計推論: 只要承諾保持成立,改變內部做法就不應單憑這件事讓契約檢查失敗。
若公開結果不足以觀察某項承諾,要先確認可用的觀察方式與測試範圍,不能假裝已經驗證。 例如只檢查成功結果中的新標題,沒有再觀察清單,就無法排除「回傳正確,但實際未設定」的缺陷。
測試替身(test double)以受控制的行為代替真實元件,方便重現特定情況。 但若把真正的標題操作換成預先設定「一定回傳成功」的替身,通過只能說明呼叫端符合這個模型; 它沒有證明真實提供者會回傳同樣結果,更沒有證明實際清單資料正確改變。 這是從替代範圍得出的限制,判讀結果時要寫清楚哪些元件是真的、哪些被替換。
schema、契約與整合測試,分別觀察什麼
測試名稱在團隊與工具之間可能不同,先交代驗證目的與實際接上的元件範圍,比只貼標籤清楚。 Pact 官方也指出,其他文件的「契約測試」可能指提供者對照文件的檢查; Pact 自己則以分別驗證雙端訊息理解的 integration contract testing 為主。 參考 Pact:契約測試定義與術語。
| 檢查方式 | 主要回答的問題 | 不能單憑它推定什麼 |
|---|---|---|
| schema 驗證 | 某份資料是否符合 schema 已表達的型別、必要欄位與值域限制? | 操作已執行、舊值取對,或狀態變化符合承諾 |
| consumer/provider 契約測試 | 呼叫端實際送出與理解的內容,是否與提供者能接受及回傳的內容一致? | 提供者的全部業務行為與副作用都正確 |
| 整合測試(integration test) | 兩個以上實際元件接在一起,能否共同產生預期結果? | 沒接入的依賴、其他環境或所有版本組合也都正確 |
schema 那列以 JSON Schema Draft 2020-12 為具體例子:它的驗證詞彙對 JSON 資料施加限制, 不能單從一份輸入資料通過驗證,推出操作後狀態的結論。 參考 JSON Schema:驗證詞彙。 Microsoft 的整合測試指南以多個元件共同產生預期結果來說明整合,並討論資料庫等基礎設施。 這是 ASP.NET Core 的具體指南;本文借用其範圍區分,不要求所有 API 使用該框架或資料庫。 參考 Microsoft:Introduction to integration tests。
用 Pact 看雙端契約驗證
Pact 是契約測試的一種具體方法,並非所有 API 契約測試的唯一方式。 以其請求/回應流程為例,consumer 測試用模擬提供者檢查呼叫端送出的內容, 並由測試斷言呼叫端能正確理解預期回應;測試產生描述互動的契約。 provider verification 再把契約中的請求送給真正的提供者,對照實際回應與契約期待。 因此,只有 consumer 對替身的測試通過,還缺少提供者一側的驗證。 參考 Pact:How Pact works。
Pact 的這類檢查聚焦互動訊息,官方明確把副作用是否正確留給提供者自己的功能測試。 對本文標題例子,雙端同意成功結果的形狀,仍不足以代替「標題確實改變、項目不變」的觀察。 前面的完整案例表是提供者行為的檢查方向,不表示必須把每個輸入邊界都塞進 Pact consumer 契約。 參考 Pact:Contract Tests vs Functional Tests。
契約測試與整合測試也不是必然互斥的標籤:一個描述驗證目的,一個可以描述元件連接範圍。 例如把實際呼叫端與提供者接起來檢查已約定的結果,可以同時具有這兩種性質; Pact 則示範如何在兩端分開執行時驗證互動契約。這是本文依目的與範圍作的分類方式。
測試通過代表多少證據
依前面的案例設計,通過表示指定版本在該環境、初始狀態與輸入下,滿足了實際寫出的斷言。 沒有檢查項目,就不能宣稱項目不變;沒有接入真實依賴,就不能把替身結果當作該依賴的整合證據。 這是對測試觀察範圍的推論,不是「所有情況都正確」的證明。
程式碼覆蓋率也不能代替契約完整性。走到某段程式,不代表已檢查舊、新標題的順序或所有失敗語意。 Pact FAQ 同樣提醒,覆蓋率無法告訴你是否涵蓋每一種語意變化。 參考 Pact FAQ:契約測試覆蓋範圍。
相關文件
- 介面、契約與責任:確認哪些行為是公開承諾。
- 輸入、輸出與失敗語意:查看標題例子的原始規則與失敗保證。
- 相容性與演進:把新舊版本的行為差異轉成需要驗證的組合。
- 文件與契約描述:追溯測試預期所根據的文字與結構化描述。
- 資源、表示與訊息:進入 HTTP 分支,理解具體協定中的請求、回應與內容。
- API 共通知識索引:查看共通概念與其他分支。