CI/CD 整合與那些年我們踩過的測試坑
一句話總結:測試塞進 CI/CD 的原則就是 Fail Fast——快的先跑,慢的後跑,壞了立刻停。
結論先講:再好的測試策略,如果沒有整合進 CI/CD,都只是「有空才跑」的擺設。而整合之後,你會遇到一堆新的問題——這篇就是在聊怎麼解決它們。
CI/CD Pipeline 的測試順序
把測試整合進 pipeline 的核心原則是 Fail Fast——讓便宜的測試先跑,貴的後跑。壞了就立刻停,不要浪費後面的計算資源。
正確的順序:
- Lint + Type Check(秒級)→ 語法問題秒殺
- Unit Test(秒到分鐘)→ 邏輯問題快速暴露
- Build(分鐘)→ 編譯問題在這擋
- Integration Test(分鐘)→ 元件互動問題
- E2E Test(數分鐘到十幾分鐘)→ 使用者流程驗證
- 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 + Integration | Unit 隔離一切測邏輯,Integration 連真實服務測互動 |
| E2E + 壓力測試 | E2E 只測最值錢的路徑,壓力測試找系統天花板 |
| CI/CD + 陷阱 | Fail Fast 原則,六個常見坑的實戰解法 |
測試策略不是一次定好就不動的。專案長大了、團隊變了、系統複雜度上去了,你的測試策略也要跟著調整。但不管怎麼調,核心精神不變:在對的層級寫對的測試,用最少的成本得到最大的信心。
系列文章:
- 測試策略(一):測試金字塔
- 測試策略(二):Unit Test 與 Integration Test
- 測試策略(三):E2E 測試與壓力測試
- 你在這裡 → 測試策略(四):CI/CD 整合與常見陷阱
延伸閱讀:
「CI 全綠不代表沒 bug,但 CI 全紅一定代表有問題——至少代表你的測試還活著。」