「如果你的 API 自動化測試只檢查
response.status_code == 200,那它就跟一個只會回傳『我還活著』但內容全錯的無人客服沒兩樣。」
大家好,我是 Jane,一個每天在第一線處理跨平台(Web & App)自動化測試、跟 CI/CD Pipeline 與 API 架構奮戰的自動化測試工程師(SDET)。
在上集 Day 19 中,我們剖析了為什麼 API 測試比 UI 測試划算 10 倍,並確立了「將邏輯驗證下沉至 API 層」的策略。
然而,當我開始進行 API 測試 Code Review 時,最常看到的「假安全感」程式碼就是:
Python
response = requests.get("https://api.example.com/user/123")
assert response.status_code == 200 # 寫完,Pass!
這種測試非常危險!因為在真實世界中,API 回傳 200 OK 的同時,可能藏著無數致命缺陷:
200 OK,但 data 欄位是 null。userId 變成 user_id),導致 Web 與 App 靜默崩潰(Silent Failure)。200 OK 並重複扣款(缺乏冪等性 Idempotency)。今天這篇文章,我們就來好好聊聊:一套真正有價值的 API 自動化測試,除了 200 OK 之外,還應該包含哪 7 大關鍵驗證?
┌─────────────────────────┐
│ API 深度驗證 7 大維度 │
└────────────┬────────────┘
│
┌───────────────────────────┼───────────────────────────┐
▼ ▼ ▼
【結構與內容】 【商業與安全性】 【狀態與機制】
1. Response Schema 結構驗證 4. 權限與越權檢驗 (BAC) 6. 狀態變化驗證 (DB/State)
2. 關鍵欄位型態與數值 5. 邊界值與異常 Error Handling 7. 冪等性驗證 (Idempotency)
3. 業務 logic 斷言 (Business)
驗證 JSON 的 Key 名稱、資料型態(String, Int, Array)、是否允許 Null,以及必填欄位是否存在。這是防止前端 Web/App 閃退的第一道防線。
針對計算型欄位(如:折扣後總金額、稅金、運費計算),驗證其演算法結果是否精準無誤。
驗證 API 的商業限制,例如:「庫存為 0 的商品無法建立訂單」、「過期優惠券無法折抵」。
401 Unauthorized。403 Forbidden。輸入超長字串、Null、SQL Injection 常用字元或負數,驗證 API 能回傳結構統一且友善的錯誤 JSON(如 400 Bad Request),而不是崩潰噴出 500 Internal Server Error 與StackTrace。
發送 POST/PUT/DELETE 請求後,除了檢查當下 Response,還要發送 GET 請求或查詢 DB,確認資料狀態確實被更新了。
對於轉帳、付款或建立訂單等 API,連續發送 2 次帶有相同 Idempotency-Key 的請求,第二次呼叫應被攔截或回傳相同結果,絕不能造成重複扣款或重複建立資料。
在 Python 生態系中,強烈推薦結合 pydantic 套件來做 API Response Schema 驗證。它比手動寫十幾個 assert key in data 快且精準 10 倍!
schemas/user_schema.py)Python
# schemas/user_schema.py
from pydantic import BaseModel, EmailStr, Field
from typing import Optional, List
class UserProfileSchema(BaseModel):
id: str
username: str
email: EmailStr # 自動驗證 Email 格式
role: str = Field(..., pattern="^(admin|regular|vip)$") # 限制 Enum 值
is_active: bool
phone: Optional[str] = None # 允許為 None 或 String
class UserListResponseSchema(BaseModel):
total: int = Field(ge=0) # 必須大於等於 0
users: List[UserProfileSchema]
tests/api/test_user_api.py)結合 Schema 驗證、狀態碼、權限與商業邏輯的完整 API 測試案例:
Python
# tests/api/test_user_api.py
import pytest
import requests
from pydantic import ValidationError
from schemas.user_schema import UserListResponseSchema
from config.settings import settings
def test_get_user_list_schema_and_business_rules(auth_headers: dict):
"""
測試情境:驗證使用者列表 API 的 Schema 結構、資料型態與內容 logic
"""
url = f"{settings.BASE_URL}/api/v1/users?page=1&limit=10"
response = requests.get(url, headers=auth_headers)
# 1. 基礎狀態碼斷言
assert response.status_code == 200
# 2. 深度 Schema 驗證 (使用 Pydantic)
# 如果後端改了欄位名稱或型態錯誤,ValidationError 會立刻印出精確的欄位差錯!
try:
validated_data = UserListResponseSchema(**response.json())
except ValidationError as e:
pytest.fail(f"API Response Schema 驗證失敗:\n{e}")
# 3. 業務 Logic 斷言
assert validated_data.total >= len(validated_data.users)
assert len(validated_data.users) <= 10 # 驗證分頁 limit=10 生效
def test_payment_idempotency(auth_headers: dict):
"""
測試情境:驗證付款 API 的冪等性 (Idempotency)
"""
url = f"{settings.BASE_URL}/api/v1/payments"
idempotency_key = "test_key_uuid_998123"
headers = {**auth_headers, "X-Idempotency-Key": idempotency_key}
payload = {"order_id": "ORD_881", "amount": 500}
# 第一次呼叫:應該成功扣款
res1 = requests.post(url, json=payload, headers=headers)
assert res1.status_code == 201
tx_id_1 = res1.json()["transaction_id"]
# 第二次呼叫:帶相同的 Idempotency-Key,應該回傳相同交易結果或被攔截
res2 = requests.post(url, json=payload, headers=headers)
assert res2.status_code in (200, 201)
tx_id_2 = res2.json()["transaction_id"]
# 驗證第二次並沒有產生全新的交易單號 (防止重複扣款)
assert tx_id_1 == tx_id_2
恭喜你!到今天 Day 20 為止,我們完成了《第四部分:測試資料、環境與 API 整合》。
我們解決了:
從明天開始,我們將邁入整條自動化流水線的最關鍵一環:
《第五部分:讓測試真正進入 CI/CD》!
明天 Day 21,我們將手把手討論:
《 Day 21|測試在 Pipeline 裡應該放在哪裡?建構高效的 CI/CD 測試階段 》。