</> 技術筆記Tech Notes

AI 寫得快了,我反而花更多時間證明它沒寫錯

程式碼產出時間變短後,測試與環境驗證成了比較長的那一段

有一次我用 AI 把規格、測試、後端修正和上版流程跑了一輪。程式碼真的生得很快,快到我還沒喝完一杯咖啡,它已經改了好幾個模組。

那天最後花最多時間的,卻不是寫程式。

我在補測試、修測試抓到的 bug、整理測試資料、接假伺服器,還要確認整套東西絕對不會碰到正式帳號。AI 把「寫出來」壓到幾分鐘,「證明它真的對」還是花了幾個小時。

那次之後,我不太敢再用產出多少程式碼衡量 AI 開發有沒有變快。

看起來會跑,業務行為還是可能錯

那天抓到一個很典型的問題。批次工作收到失敗結果後,有把失敗清單回傳,卻忘了把那些工作標成「需要人工處理」。

API 沒有噴錯,畫面也拿得到回應。單看程式碼,每一段都很合理。但失敗的工作會卡在原本狀態,沒有人知道要接手。

這種錯最麻煩,因為它不是語法錯,也不是服務起不來。它要拿業務規則去問:「失敗後,狀態應該變成什麼?」才抓得到。

最後那批模組補出 70 條單元測試。數字本身不是重點,重點是其中一條把這個真 bug 釘出來了。

我把測試分成不同距離

我不會用 100 條 E2E 去保護所有細節。越接近真實使用者,測試越慢、相依越多,也越容易因環境波動失敗。

70 條單元測試守規則,32 條 Mock E2E 守畫面流程,全端 E2E 只守高風險外部發布

我會把它們分成幾層:

測試層 它負責回答的問題
單元測試 某個業務規則或狀態轉換對不對
整合測試 幾個模組和真實資料庫接起來後是否正常
UI/Component Loading、空資料、錯誤和操作狀態是否正確
E2E 使用者從登入到完成主要流程是否走得通

底層測試便宜,適合跑很多;最上層測試只守住少量重要流程。人工驗收還是要留,但把每次改版都一樣的點擊工作交給人,速度一定追不上 AI 改程式的頻率。

Mock E2E 很快,但它只驗到前端

那次我用 Playwright 的 page.route 攔掉後端請求,讓前端收到固定回應。32 條 Mock E2E 都能快速跑,也真的抓到一個回歸:我改了確認按鈕的 data-testid,忘了同步既有測試。

這一層很適合放在 CI。它能驗證按鈕按下去後,前端送了什麼、畫面怎麼變、錯誤有沒有顯示。

但它有一個很清楚的天花板:後端根本沒跑。

前端送對了,不代表後端組出來的外部 API payload 也對;Mock 回一個成功,不代表正式程式真的會打到正確端點。

外部 API 不能拿正式帳號測

我碰到的流程會把內容發到外部平台。要做全端 E2E,就得讓真的後端一路跑到最後。但我不可能為了測試,真的往正式帳號發一篇貼文,再祈禱刪除流程沒有壞。

我後來做了一個 Fake API。它使用和外部平台相同的測試端點形狀,收下後端送來的請求,記錄必要欄位,再回傳平台格式的假結果。

正式後端只換 base_url 就改打 Fake API,測試檢查 endpoint、page_id 與遮罩;寫死的刪除網址則會直接漏到正式平台

測試可以檢查:

  • 打到哪一個 endpoint。

  • 帳號識別碼是否正確。

  • 文案和圖片網址有沒有送齊。

  • 失敗回應後,系統狀態有沒有正確改變。

  • Log 裡的 access token 是否已遮罩。

這個小工具也逼出另一個問題:刪除貼文的 URL 被寫死成正式網址。建立貼文可以導向測試環境,刪除卻會往正式平台走。症狀是新增測試都正常,一跑刪除就準備連外。原因是程式只有一半使用可設定的 base_url

修法不是在測試裡特判,而是把所有外部端點都放到同一個可替換設定,並在測試環境加上禁止連外的保護。

假平台越像真的,安全邊界越要明確

Fake API 不是隨便在本機開個服務就算完成。我會要求幾個限制:

  • 只能在測試設定啟用。

  • 測試憑證和正式憑證完全分開。

  • Token 在落 Log 前就遮罩。

  • CI 預設不能連到外部平台網域。

  • 測試結束後清掉收到的 payload。

如果要用本機 DNS 或憑證,讓正式程式碼在不修改主流程的情況下導向 Fake API,隔離要做得更嚴。這種做法很接近真實,也因此更不能讓設定誤入正式環境。

Docker 解決的是重現,不是真理

全端 E2E 同時需要資料庫、後端、Fake API、前端和測試資料。Docker Compose 很適合把它們寫成同一份環境,讓本機與 CI 都能用相同指令啟動和清理。

不過我不會說用了 Docker,所有失敗就一定是程式邏輯。映像版本、健康檢查、網路和啟動順序仍然可能出錯。Docker 真正幫到我的,是把環境差異縮小,並讓這些差異有檔案可查,不再藏在某台電腦裡。

docker compose -f compose.e2e.yml up -d --wait
dotnet test tests/E2E
docker compose -f compose.e2e.yml down -v

最後的 -v 會移除 compose 建立的具名與匿名 volume。這正是測試需要的重置行為,也表示不能拿同一份指令去清理保存正式資料的環境。

那一天真正完成的東西

回頭看,那天的產出不是幾個 commit,而是一條可以重跑的驗證路線:

  • 70 條單元測試守住業務規則。

  • 32 條 Mock E2E 守住前端主要流程。

  • Fake API 接住外部發布,不碰正式帳號。

  • Docker Compose 把全端環境變成可啟動、可清理的設定。

  • 測試抓到狀態漏改、測試識別碼不同步和正式 URL 寫死等問題。

如果沒有後面這些,AI 只是更快交出一批「看起來完成」的程式碼。

我現在願意讓 AI 多做一點,不是因為它更有自信,而是因為失敗時有測試會紅、有 payload 可以查、有環境可以重建。能把錯誤變成明確訊號後,速度才真的有用。