CI/CD 整合與那些年我們踩過的測試坑

一句話總結:測試塞進 CI/CD 的原則就是 Fail Fast——快的先跑,慢的後跑,壞了立刻停。

結論先講:再好的測試策略,如果沒有整合進 CI/CD,都只是「有空才跑」的擺設。而整合之後,你會遇到一堆新的問題——這篇就是在聊怎麼解決它們。

CI/CD Pipeline 的測試順序

把測試整合進 pipeline 的核心原則是 Fail Fast——讓便宜的測試先跑,貴的後跑。壞了就立刻停,不要浪費後面的計算資源。

正確的順序:

  1. Lint + Type Check(秒級)→ 語法問題秒殺
  2. Unit Test(秒到分鐘)→ 邏輯問題快速暴露
  3. Build(分鐘)→ 編譯問題在這擋
  4. Integration Test(分鐘)→ 元件互動問題
  5. E2E Test(數分鐘到十幾分鐘)→ 使用者流程驗證
  6. Load Test(十幾分鐘到幾十分鐘)→ 效能基準

如果 Unit Test 就爆了,後面的 E2E 和 Load Test 根本不用跑。省時間、省機器、省電費。

覆蓋率門檻

在 CI 裡設定覆蓋率門檻,作為合併 code 的必要條件:

  • 新 code 的覆蓋率不得低於 80%
  • 整體覆蓋率不得下降超過 1%
  • 核心模組不得低於 90%

這些數字不是拿來整人的,是拿來防止有人「先上再說」然後補測試永遠在 backlog 裡長灰塵。

測試報告

  • JUnit XML 格式讓 GitLab / GitHub 直接在 MR 裡顯示結果
  • Cobertura 格式支援行級的覆蓋率標示——哪一行沒被測到一目了然
  • 壓力測試結果推到 Grafana Dashboard,建立歷史趨勢

六個最常踩的坑

坑一:Flaky Test

現象:同一份測試有時過有時不過,沒改任何 code。你跑三次,過兩次失敗一次,然後你把那次失敗當成「靈異事件」忽略掉。

別忽略。Flaky test 就像房子裡的白蟻——你不處理它,它會把整個測試套件的可信度吃光光。當團隊習慣性地把 CI 失敗當成「應該是 flaky」,你的測試就等於沒有了。

常見原因

  • 測試之間共享狀態沒清乾淨
  • 依賴外部服務的回應時間(網路不穩)
  • setTimeout 或固定延遲的時間假設
  • 非同步操作的 race condition

解法

  • beforeEach / afterEach 確實重設狀態
  • 用 retry 止血,但同時追蹤根因
  • 建立 Flaky Test Dashboard,定期清理

坑二:測試套件跑太慢

現象:CI 跑一輪要 30 分鐘以上。工程師開完 PR 之後去泡咖啡、逛 Reddit、吃完午餐回來 CI 還在跑。

解法

  • 平行化:Jest --shard、Cypress --parallel,把測試分到多個 job 同時跑
  • 分層執行:MR 只跑 Unit + Integration,E2E 和 Load Test 等合併到 main 之後再跑
  • 快取:CI 裡快取 node_modules,不要每次都重新安裝
  • 增量測試jest --changedSince 只跑被改到的檔案相關的測試
  • 清理慢測試:設定單一測試的超時限制,超過的標記出來處理

坑三:過度 Mock

現象:所有測試都綠的,上線卻一堆 bug。因為你的 Mock 跟真實服務的行為根本不一樣——你測的是一個平行宇宙的系統。

這是一個很微妙的陷阱。Mock 給你的是假的安全感。

解法

  • Mock 只用在你不需要測試的邊界(例如第三方金流 API)
  • 自己的服務盡量連真的(用 Testcontainers 起臨時服務)
  • 定期跑 Contract Test 確保 Mock 的行為跟真實服務一致
  • 能用 Record/Replay 機制就別手寫 Mock

坑四:忽略非功能性測試

現象:功能測試全過,上線後 P99 延遲 3 秒,使用者排隊投訴,PM 在 Slack 上 @你。

解法

  • 壓力測試納入 CI(至少在 main 分支跑)
  • 定義效能 SLA,設成 k6 的 threshold
  • 建立效能基線,每次 release 跟基線比較
  • 不要只看回應時間,也看 CPU、Memory 使用率

坑五:測試環境不穩定

現象:本機全過,CI 失敗。或反過來——CI 全過,本機跑不動。

解法

  • Docker 統一開發跟 CI 的測試環境
  • 測試用的 DB、Redis 用 Testcontainers 而不是共用的遠端服務
  • 避免依賴特定系統時區或語系
  • 測試資料要獨立——每個測試用自己的資料集

坑六:只有 Happy Path

現象:測試全過但只是因為你只測了「一切正常」的情境。使用者一輸入奇怪的東西、一斷網、一超時,系統就炸了。

解法

  • 每個功能至少測三條路:正常、替代、異常
  • 特別注意空值、超長字串、特殊字元、併發操作
  • 用 mutation testing(變異測試)檢驗你的測試到底有沒有在保護 code

系列總結

四篇走完了整個測試策略:

篇章核心重點
測試金字塔底層多寫、頂層少寫,覆蓋率是指標不是目標
Unit + IntegrationUnit 隔離一切測邏輯,Integration 連真實服務測互動
E2E + 壓力測試E2E 只測最值錢的路徑,壓力測試找系統天花板
CI/CD + 陷阱Fail Fast 原則,六個常見坑的實戰解法

測試策略不是一次定好就不動的。專案長大了、團隊變了、系統複雜度上去了,你的測試策略也要跟著調整。但不管怎麼調,核心精神不變:在對的層級寫對的測試,用最少的成本得到最大的信心。

系列文章:

延伸閱讀:

「CI 全綠不代表沒 bug,但 CI 全紅一定代表有問題——至少代表你的測試還活著。」