真實後端驗證測試
流程測試的假後端固化「已知」的後端行為;本章處理另一半:對真實後端斷言這些行為,讓後端行為漂移有地方現形。適用前提:後端服務無法本機啟動、只有共用測試環境(staging)可用時,實機取證沒有可拋棄的本機後端可對,這一層便以常駐驗證測試的形態存在。這一層的工程化細節決定它是「一直活著的防線」還是「寫完就被遺忘的儀式」——本章的每一條設計都對應一個實際踩過的歧路。
容易走錯的形態決策
寫成正規測試、不寫腳本
寫一支獨立腳本(登入、打 API、印結果、人眼判讀)。歧路在於:腳本的結論靠人讀輸出,跑完即散;測試把結論寫成斷言——紅綠本身就是答案,且失敗訊息(reason)可以內建處置指引:「後端未釋放資源 → 與後端確認並同步修正前端編排與假後端」。同一份驗證,腳本是一次性動作,測試是可重跑的資產。帶 exit code 與告警、排入 CI 的排程腳本能補上「一次性動作」的缺口,但補不了本機執行路徑的可見性——開發者在本機跑整合套件時看不到它——所以形態仍收斂為測試。
併入整合套件、不設獨立分類
把它放進獨立目錄(calibration/、e2e/)。問題的機制:獨立目錄的測試不在任何預設執行路徑上,執行依賴個人記憶,而記憶不進 onboarding——人員更替後防線無聲消失。放進整合測試同一個目錄,跑整合套件時它一起出現——執行不了時以「跳過」現身在輸出裡,跳過計數是每次執行都出現的持續訊號,把「這條防線沒開」持續放在眼前,而不是無聲缺席。
預設可執行,而不是預設跳過
用執行參數當開關(不帶憑證參數就跳過)。看似謹慎,實際效果是:從 IDE 直接執行測試的人永遠帶不了參數,防線對他們永遠是關的。修正後的預設:
| 情況 | 行為 | 理由 |
|---|---|---|
| 直接執行(IDE、無參數) | 以內建的測試環境帳號直接執行 | 前提:測試環境憑證已在版本庫、且團隊接受此暴露面(見下段) |
| 連不上後端(離線) | 執行期偵測、降級為跳過(附原因) | 環境問題與程式錯誤分開呈現:環境狀態走跳過、紅燈保留給需要人修的問題 |
| 連得上但登入被拒 | 明確失敗 | 內建帳號失效=防線悄悄關閉,必須有人看到並更新帳密 |
| 指向生產環境 | 拒絕執行 | 驗證會建立與刪除真實資料 |
「內建測試帳號直接執行」是條件句:前提是測試環境憑證已存在於版本庫(例如開發登入頁的預填帳號)、且團隊已知情地接受這個暴露面——這是特定專案的既成條件,前提成立才沿用。前提不成立時,改用本機 gitignore 的憑證檔;此時「找不到憑證檔」比照登入被拒亮紅、不比照離線跳過——憑證檔缺失是每台機器補一次就好的環境設定債,紅燈逼人把它補完,跳過會讓它永遠停在未設定。
「指向生產環境拒絕執行」需要明確的判定機制:環境 URL allowlist、環境變數旗標、或 host 比對——擇一實作,判定失敗時預設拒絕。
「連不上→跳過」與「登入被拒→失敗」的語意區分是這組設計裡分量最重的一條區分:兩者都會讓驗證無法進行,但前者是環境的暫時狀態,後者是需要人介入的資產腐化。實作上要把連線層錯誤(TCP、DNS)與應用層拒絕(HTTP 4xx)分開捕捉——單一 try-catch 把兩者一起吞掉時,登入被拒也會被歸成跳過。
請求層:走與產品相同的 client
驗證測試的請求走產品自己的 API client 與模型解析。兩個理由:
- 不重解產品已解決的問題。實際案例:raw HTTP 手刻要自己處理列表回應的分頁包裝,而產品的回應模型早就內建了這層解析——手刻等於重新發現一次已知問題。
- 順帶驗證解析層。走同一套模型,後端回應形狀改變(多包一層、欄位改名)會在這裡先於產品爆出來。
代價是要繞開產品 client 的執行環境依賴(DI 容器、攔截器依賴的登入服務與翻譯資源):手組傳輸層、認證憑證於登入後手動掛上——這屬於一次性的組裝成本;後端每次改回應形狀,手刻版都要獨立再修一次,走產品 client 的測試自動繼承產品層的修正。判準也在這裡收攏:登入請求是唯一允許手刻的請求——它一次性、形狀穩定;能複用產品登入 API 的回應模型時,只手組傳輸層即可。
另一個環境陷阱:UI 測試框架的綁定常會把 HTTP client 換成假件、擋掉真實網路。真實後端驗證測試不可初始化那個綁定,也因此不可與流程測試共用需要綁定的 harness——這是檔案層級的硬約束,值得寫在檔頭。
劇本設計:完整生命週期+現場復原
驗證的單位是「後端動詞的效果」,最有價值的形態是生命週期劇本。例如訂單:
- 建立前置(佔用一個資源、加入一件真實商品——商品從目錄 API 現查,不寫死)
- 結帳 → 斷言:訂單記錄查得到、狀態為已完成、關聯資源已釋放
- 取消 → 斷言:再查同一筆,狀態轉為已取消
配套紀律:
- 先復原、再斷言:把現場復原(釋放資源、刪除臨時資料)放在斷言之前或 finally 裡——斷言失敗的那次執行更不該留下髒資料。
- 容許非同步:狀態類斷言即刻讀一次、延遲再讀一次,區分「同步生效/非同步生效/未生效」,三種結果對前端的意義不同(T.C7)。延遲時間取實測觀察到的後端非同步生效窗乘上安全係數——觀察窗穩定在 N 秒內時,2-3 倍即可(觀察到 1 秒內生效 → 延遲 2-3 秒);觀察窗跨越級距(有時 1 秒有時 10 秒)代表後端行為本身不確定,倍數幫不上忙,改用重試迴圈(重讀 + 上限次數)。設太短會把非同步生效誤判成未生效。
- 接受的代價要寫明:每次執行都會對測試環境產生真實讀寫。資料噪音、與他人並行執行的碰撞風險,是這層防線的持有成本——團隊要知情地接受,而不是事後驚訝。碰撞的緩解選單:專屬測試帳號、測試資源命名前綴、或套件 serial 執行。
CI 執行節奏
形態決策回答了「開發者在本機跑整合套件時,這層防線如何現身」;CI 是第二個執行環境,節奏依 CI 與測試後端的連通性映射:
- CI 連得到測試後端 → 設獨立 stage 執行,頻率在每 PR 與 nightly 之間擇一:測試後端穩定、資料碰撞少時走每 PR,讓後端行為漂移在合併前現形;後端常態不穩或並行資料互踩頻繁時退到 nightly,用較低的頻率換穩定的訊號品質。走本機 gitignore 憑證分支的團隊開 CI stage 時,憑證由 CI secret 注入(環境變數或臨時檔)——否則「找不到憑證檔亮紅」的設計會讓 CI 恆紅。
- CI 連不到測試後端 → 這層防線留在本機,CI 持續記錄 skip 計數、且指定有人定期看——skip 計數持續上升是防線失效的訊號:連本機也沒有人在跑。行動閾值的方向:連續 N 天全 skip 即視為防線失效。N 的校準邏輯:團隊若每天在本機跑一次整合套件,N = 5(一週營業日)代表「一整週沒有人跑過」;每週跑一次的團隊 N = 3(三週)。超過 N 就該確認是環境不可達還是無人跑——兩者的處置不同。
紅燈的分診有順序:第一步先重跑一次——共用環境的並行噪音多為暫時;重跑仍紅,查同時段是否有他人對同一環境執行;再查測試資料是否被改壞。還有一個要先排除的形態:連得上、登入也成功,但環境前置資料缺失(商品目錄是空的、seed 資料被清)——劇本在建立前置那步就失敗,亮的是紅燈、成因卻是環境資料狀態。這些都排除後,持續紅才升級為後端行為漂移的調查。
與假後端的配對關係
這層測試與語意級假後端是同一份行為知識的兩面:假後端寫「我們認為後端會這樣做」,驗證測試證明「後端現在確實這樣做」。維護慣例一句話:假後端每新增或修改一條行為,就在驗證測試補一條對應斷言——把這句話寫在假後端的檔頭,讓兩邊不脫鉤。
這條防線的證明範圍止於測試環境的後端行為:驗證測試綠燈代表「測試環境的後端現在確實這樣做」,測試環境與生產環境的一致性是另一個問題,歸共用測試環境的設計契約管。
下一步路由
- 配對的另一半 → 語意級假後端與流程測試
- 責任歸因的完整案例 → T.C7 症狀相同、成因兩種
- 這類測試的成本判斷 → 協議整合測試的成本判斷
- 供給側的視角:QA 站該對這類消費者承諾什麼 → 共用測試環境的設計契約