iT邦幫忙

2026 iThome 鐵人賽

DAY 12
0

❯❯ 當 Mock 遇到了真 DB:7 處寫入讀取不對稱缺口,與一個 Commit 切換 Postgres 的真相
Day 12 你整套都在跟假後端玩,真的一接不就露餡了

📍 流水線位置|入口 → flow → 型別 → 【mock】 → 測試 → UI → vibe → 上線(mock 與真實的接縫)

流水線位置|入口 → flow → 型別 → 【mock】 → 測試 → UI → vibe → 上線(mock 與真實的接縫)

討論到第十二天,是時候來面對這套架構最常遭遇的質疑了(抖):
系統若長期建立於 Mock Server、Typed Client 與全數通過的 E2E 自動化測試上,是否只是在受控的隔離環境中進行驗證?當系統正式與實體後端及資料庫串接時,會不會暴露大量的架構落差與隱形缺口?

答案是:會😂


寫入與讀取介面的不對稱:7 個隱形的大坑

在此架構中,Mock Server 的資料狀態只留存於 Server 的記憶體陣列中,服務重新啟動就會重置為預設 Seed 資料。在串接實體資料庫之前,系統其實並未落實實體的資料持久化(Persistence)。

在準備切換到實體資料庫的預檢階段,我針對各模組進行了「寫入後到底能不能讀出來」的對帳檢查,沒想到卻發現了系統中存在 7 處寫入後無法經由讀取端點還原的結構性缺口。

追下去才發現,源頭竟出在需求規格(Specification)本身就定義偏了:
當初規格採行命令驅動(Command-driven)撰寫,明確規範了各項寫入動作(如「新增賓客」、「更新座位設定」),自動化生成腳本因而精確產生了寫入端點(HTTP POST / PUT)。

然而,部分業務狀態只定義了寫入行為,卻完全忘了規範對應的讀取檢索行為(HTTP GET)。這導致對應的讀取端點出現兩種情況:未被自動化腳本生成,或是生成的端點回應中遺漏了關鍵欄位。

偏偏在 UI 自動化生成階段,為了滿足前端元件的渲染與運作需求,生成腳本使用了前端區域狀態(Local State,如 ref / reactive)暫存寫入的資料。使用者進行儲存操作時,介面呈現成功更新的狀態,但該狀態僅存在於前端記憶體中,並未由 Server 端點返回。只要你按下 F5 重新整理,頁面重新向 Mock Server 抓資料,瞬間被打回原形、資料全數歸零。

那為什麼健檢當下我那 94 支自動化 E2E 測試完全沒抓到?

因為所有的測試案例都在單一 Page Session 裡一氣呵成:觸發寫入操作 → 斷言介面 DOM 改變 → 測試通過。沒有一支測試案例包含「重新載入頁面並驗證 Server 狀態」的步驟。

「寫入與讀取介面的不對稱」與「缺乏重新載入驗證的測試案例」兩者交疊,導致此類持久化缺口在 Mock 階段未能及時被自動化測試捕捉,我就這樣傻傻被騙過去了🥲。


補齊讀取端點與持久化測試驗證

抓到問題後,修復此類缺口的工程步驟包含:補齊缺漏的 GET 端點、補全回應欄位,並將前端介面由 Local State 暫存改為透過 useFetch 向 Server 端點請求狀態。

為防止這種「假持久化」的悲劇再次發生,我建立了專門驗證持久化行為的測試套件(persistence-*.spec.ts),強制所有的測試流程必須包含頁面重新載入(Reload)的驗證步驟。
以場地佈局模組之測試片段為例:

const apiCall = waitForApiCall(page, /\/venue-layout(\?|$)/, 'PUT')
await page.getByTestId('venue-submit').click()
// 等 PUT response 完成(DB 已 commit)再 reload,避免 GET 搶在寫入前讀到舊值的競態
const request = await apiCall
await request.response()

// 重整後重開 modal:值應由 GET 還原為剛存的,而非硬編預設
await page.reload({ waitUntil: 'networkidle' })
await page.getByTestId('venue-layout').click()
await expect(page.getByTestId('stage-width')).toHaveValue('999')

持久化測試與重新載入驗證

這套測試驗證流程是:發起寫入請求 → 確認 Server 端點回應完成 → 強制重新載入頁面 → 觸發讀取端點並斷言資料正確性。透過此流程,率先鋪設的 LINE 訊息、場地佈局與謝卡喜餅三個核心模組,再也無法用前端區域狀態掩蓋端點資料缺口。

這裡說明一下,雖然 7 處缺口的讀取端點已全數補齊,但這種重型持久化測試目前只覆蓋在這三個模組上,其餘模組尚未納入,這是刻意的取捨而非盲目追求 100% 覆蓋。

**※ 註:**程式碼中關於「避免 GET 搶在寫入前讀到舊值的競態」的註解,是後續正式串接 PostgreSQL 資料庫後,因為非同步寫入落盤延遲觸發測試失敗而補強的同步機制。


實體資料庫遷移:一次性的無痛切換

搞定 7 處介面缺口、補上持久化測試之後,就輪到實體資料庫的遷移工程:寫好 Drizzle ORM Schema、啟動本機 PostgreSQL 資料庫、導入初始化資料,並發起切換 commit(Commit 3fa5dff),將全專案 96 支 API Handler(僅 2 支特例除外)一次性由 Mock 記憶體操作切換為實體 PostgreSQL 資料庫查詢。

切換完成後,前端 UI 程式碼無需進行任何修改,全數 E2E 自動化測試當下維持綠燈通過。

這場原本以為會血流成河的串接,之所以能做到幾乎無痛,全靠先前立下的兩大工程護欄:

  1. 型別合約約束:
    Mock 端點與實體資料庫查詢共用相同的 TypeScript Interface,只要欄位型別有半點不對,編譯階段就會直接擋下。

  2. 邏輯缺口前置修復:
    介面不對稱與資料無法還原等問題,已經在對帳檢查與持久化測試階段完成修補。


結論與流程優化

Mock 階段暴露的問題,本質上並非 Mock Server 系統自身的缺陷,而是需求規格(Specification)不完整所導致的衍生風險:若規格僅定義了「寫入」而遺漏「讀取」,自動化生成工具與基礎 E2E 測試皆無法主動捕捉此類語意缺口。Mock Server 只不過是延後了這個規格漏洞曝光的時間點而已。

經過這次經驗,我升級了流水線規範:在從 Mock 遷移至實體資料庫前,必須強制對全系統欄位執行「寫入與讀取對應(CRUD Balance)」的對帳審查,確保規格定義的完整性。

這輪對帳也建立了一套標準處置節點:

狀態對帳 → 產出缺口報告 → 人工審查定案 → 補強修復 → 自動化驗證。

CRUD Balance 寫入與讀取對帳審查機制

靠這套對帳與測試的雙重約束,終於有底氣把系統切換時的接縫風險,降到趨近於零。

然而,接上實體後端只是第一步。隨之而來的下一個硬考驗是:當後端的規格與資料庫 Schema 於後續迭代中持續發生變更時,流水線應如何維持前端與後端的狀態同步?

關於相關的同步機制與 Sync 模式,明天再來跟大家聊聊!

🎒 最小一步|找一個你專案裡有儲存功能的頁面,存完按 F5 重新整理,看資料是從 Server 端點完整讀回來的,還是只活在前端記憶體裡。
📎 本篇證據|Commit 3de898a(包含 7 處介面缺口註記之 Persistence Specs)、Commit 3fa5dff(94 支 Handler 切換至 PostgreSQL 之異動紀錄)、PR #8


上一篇
Day 11 後端還沒好,但我一天都沒等
下一篇
Day 13 後端又改規格了,我不重寫!Sync 模式如何用「外科手術」精準同步增量欄位
系列文
收到自己的紅色炸彈!婚期就是 Deadline:前端一人用 AI 規格流水線,30 天出貨婚禮 SaaS14
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言