測試寫了,為什麼 production 還是出錯?——一個前端工程師的採坑紀錄
我曾經在一個專案裡認真寫測試,覆蓋率也達標了,但每次推到正式環境,還是會跳出我沒預料到的錯誤。超時錯誤一堆,主管說要把覆蓋率拉到 80%,於是我花了好幾天重構程式碼,只為了讓數字好看。
那段時間我心裡一直有個疑問:測試到底有沒有用?還是只是在做表面工夫?
現在回頭看,不是測試沒用,是我們的測試策略根本就錯了。這篇文章把我踩過的坑全部整理出來,希望你不要走一樣的路。
坑一:有寫測試,但 production 還是出錯
這是最讓人沮喪的情況:測試全部通過,信心滿滿部署,然後 bug 就出現了。
問題根源:只測了一切順利的情況。
這在測試圈有個名字叫「只測快樂路徑(Happy Path)」。你把最理想的情況測了一遍,但正式環境的使用者才不會照你的劇本走。
// 我們測了這個 ✅
it('使用者登入成功', () => { ... })
// 但我們沒測這些 ❌
it('密碼裡有特殊字元時', () => { ... })
it('網路突然中斷時', () => { ... })
it('使用者快速連按兩次送出按鈕時', () => { ... })
it('token 過期但使用者還在操作時', () => { ... })
正式環境出的問題,幾乎都藏在邊界情況裡,而不是正常流程。
另一個原因:單元測試寫太多,整合測試幾乎是零。
每個函式單獨測都過關,但這些函式組合在一起跑,就出問題了。就好比你測試了每一個零件都沒問題,但裝起來的機器卻動不了——因為你沒有測「組裝起來之後」的狀態。
還有一種情況更隱性:Mock 用太兇,把所有外部依賴都換成假的。測試環境裡一切正常,但一接上真實的 API、資料庫、環境變數,就爆了。這不是在測真實的程式,是在測一個精心佈置的假環境。
坑二:測試一堆超時錯誤
測試跑得很慢、動不動就出現 timeout,這幾乎都是同一個原因:有人在測試裡做了真實的網路請求或 I/O 操作。
// ❌ 這個測試會真的去打 API,網路不穩就 timeout
it('應該取得使用者資料', async () => {
const user = await fetchUser(1) // 真實 HTTP 請求
expect(user.name).toBe('Alice')
})
測試不應該依賴外部服務。正確的做法是把外部依賴換成假的(Mock),讓測試在毫秒內完成:
// ✅ Mock 掉 API,不發真實請求,速度快又穩定
it('應該取得使用者資料', async () => {
vi.mocked(fetchUser).mockResolvedValue({ name: 'Alice' })
const user = await fetchUser(1)
expect(user.name).toBe('Alice')
})
如果你的測試套件跑起來很慢,或是會隨機失敗(Flaky Tests),通常就是測試裡藏著真實的網路呼叫或檔案操作。找出來,換成 Mock,問題就解決了。
坑三:為了覆蓋率而重構程式碼
這是最本末倒置的一件事,但它真實發生在很多團隊裡。
主管說要達到 80% 覆蓋率,然後工程師開始把程式碼拆小、把私有邏輯抽出來,目的不是為了讓程式架構更好,而是「拆開來才能單獨測、數字才會上去」。
覆蓋率是結果,不是目標。
它的正確用途只有一個:找到你還沒測到的盲點。
覆蓋率 30% → 危險,大量程式碼從來沒跑過,有很高風險
覆蓋率 80% → 不錯,繼續補重要的路徑
覆蓋率 100% → 不代表沒 bug,可能每行都跑到了,但沒有真的驗證行為是否正確
一個覆蓋率 100% 但斷言全部寫 expect(true).toBe(true) 的測試,毫無意義。一個覆蓋率 60% 但精準針對最容易出錯的邏輯寫測試,反而更有保障。
重構程式碼讓它更好測,這本身沒有問題。但動機要對——應該是「這樣拆分架構更清晰,剛好也更好測」,而不是「不拆的話數字不好看」。
更深層的問題:測試金字塔倒過來了
業界有個概念叫「測試金字塔」,描述不同種類的測試應該佔的比例:
理想比例 很多團隊的現實
▲ E2E ▲ 單元測試(大量)
▲▲▲ 整合測試 ▲▲▲ 整合測試(很少)
▲▲▲▲▲ 單元測試(最多) ▲▲▲▲▲ E2E(幾乎沒有)
諷刺的是,很多專案的金字塔剛好搞反:寫了一大堆單元測試,每個函式都測了,覆蓋率也高,但整合測試幾乎是零——而 production 的 bug,大多數都發生在系統邊界,也就是整合的那一層。
這就是為什麼「有寫測試但還是出錯」。
那麼,什麼樣的測試才有意義?
與其追求覆蓋率數字,不如問自己這幾個問題:
這個測試,如果真的出現這種 bug,它會失敗嗎? 如果答案是「不一定」或「不會」,這個測試的價值就很低。
這個功能最怕什麼情況壞掉? 針對那個情況寫測試,而不是針對正常情況。
上次 production 出的 bug 是什麼? 補一個測試專門防止它復發,這種測試最有價值。
這個測試如果刪掉,我會不安心嗎? 不安心才留,沒感覺的測試可以考慮刪掉。
總結:測試是保護網,不是成績單
回頭看那段寫了一堆測試但還是出錯的日子,問題不在於「測試沒用」,而在於:
- 只測快樂路徑,沒有測邊界情況
- 測試裡有真實的網路請求,導致 timeout 和不穩定
- 把覆蓋率當目標而非工具,為了數字重構程式碼
- 大量單元測試,但缺少整合測試
好的測試策略不是寫越多越好,而是寫對的測試放在對的地方。少量、精準、能真的抓到問題的測試,比一百個為了覆蓋率存在的測試有意義得多。
測試是給你信心的工具,不是讓你焦慮的 KPI。如果你在一個專案裡寫測試卻一點都沒有感到安心,很可能不是測試沒用,而是測試的方向需要調整。
本文根據實際工作經驗整理,適合曾對測試感到困惑或懷疑的開發者。