還記得昨天我們學會了例外處理測試,確保程式在錯誤情況下的穩定運行嗎?今天要面對一個更深層的問題:「我們的測試到底覆蓋了多少程式碼?」
測試覆蓋率(Test Coverage)就像是程式碼的健康檢查報告,告訴你哪些程式碼被測試了,哪些還沒有。它不是萬能的,但是一個非常有用的指標,幫助我們找出測試的盲點。
我們正在第九天的基礎測試概念學習:
基礎測試概念(第 1-10 天)
├── ✅ Day 1: 環境設置與第一個測試
├── ✅ Day 2: 測試斷言
├── ✅ Day 3: TDD 紅綠重構
├── ✅ Day 4: 測試結構
├── ✅ Day 5: 生命週期
├── ✅ Day 6: 參數化測試
├── ✅ Day 7: 測試替身基礎
├── ✅ Day 8: 例外處理測試
├── 📍 Day 9: 測試覆蓋率(今天)
└── Day 10: 下階段預告
今天結束後,你將學會:
📊 測試覆蓋率讓我們能夠量化測試完整性,發現潛在的測試盲點。
覆蓋率的好處:
更新 package.json
{
"devDependencies": {
"@vitest/coverage-v8": "^1.0.0"
},
"scripts": {
"test": "vitest",
"test:coverage": "vitest --coverage"
}
}
更新 vitest.config.ts
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
coverage: {
provider: 'v8',
reporter: ['text', 'html'],
reportsDirectory: './coverage',
exclude: [
'node_modules/**',
'dist/**',
'**/*.d.ts',
'**/*.config.ts',
'tests/**'
],
thresholds: {
lines: 80,
functions: 80,
branches: 80,
statements: 80
}
}
}
})
語句覆蓋率(Statement Coverage)
分支覆蓋率(Branch Coverage)
函數覆蓋率(Function Coverage)
行覆蓋率(Line Coverage)
讓我們用一個實際的例子來理解覆蓋率的重要性。
建立 src/day09/calculator.ts
export class Calculator {
add(a: number, b: number): number {
return a + b
}
divide(a: number, b: number): number {
if (b === 0) {
throw new Error('Cannot divide by zero')
}
return a / b
}
// 這個函數可能沒被測試到
multiply(a: number, b: number): number {
return a * b
}
}
建立 tests/day09/coverage-demo.test.ts
import { describe, it, expect } from 'vitest'
import { Calculator } from '../../src/day09/calculator.js'
describe('Calculator - Coverage Demo', () => {
const calculator = new Calculator()
it('adds two numbers correctly', () => {
expect(calculator.add(2, 3)).toBe(5)
})
it('divides two numbers correctly', () => {
expect(calculator.divide(6, 2)).toBe(3)
})
it('throws error when dividing by zero', () => {
expect(() => calculator.divide(5, 0)).toThrow('Cannot divide by zero')
})
// multiply 函數沒有測試,會在報告中顯示為未覆蓋
})
執行覆蓋率測試命令:
npm run test:coverage
覆蓋率報告會顯示四種指標:
執行覆蓋率測試後,你會看到類似這樣的報告:
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-------------|---------|----------|---------|---------|-------------------
calculator.ts| 75.00 | 100.00 | 66.67 | 75.00 | 8-9
這告訴我們:
當發現未覆蓋的程式碼時,補充有意義的測試:
it('multiplies two numbers correctly', () => {
expect(calculator.multiply(3, 4)).toBe(12)
expect(calculator.multiply(-2, 5)).toBe(-10)
expect(calculator.multiply(0, 100)).toBe(0)
})
加入這個測試後,覆蓋率會提升到 100%!
// ❌ 為了覆蓋率而寫的無意義測試
it('covers getter', () => {
const obj = new MyClass()
expect(obj.getValue()).toBe(10)
})
// ✅ 有意義的測試,自然達到覆蓋率
it('calculates discounted price with tax', () => {
const calculator = new PriceCalculator()
calculator.setBasePrice(100)
calculator.applyDiscount(0.1)
calculator.addTax(0.1)
expect(calculator.getTotalPrice()).toBe(99) // 100 * 0.9 * 1.1
})
重點測試複雜的業務邏輯,而不是簡單的 getter/setter。
當覆蓋率很低時,可能表示函數太複雜,需要拆分成更小的函數。
透過今天的學習,我們掌握了:
測試覆蓋率讓我們能夠客觀衡量測試完整性,發現未測試的程式碼路徑,指導測試策略。記住,覆蓋率是品質的指標之一,但不是唯一指標!
今天的關鍵要點:
覆蓋率類型
工具配置
@vitest/coverage-v8
最佳實踐
明天是第十天,我們將總結這十天學到的基礎測試概念,並為下一階段的學習做準備。你會學到如何將這些基礎概念整合運用,打造更完整的測試策略。
今天我們深入探討了測試覆蓋率,這是 TDD 過程中重要的品質指標。我們學會了如何配置和使用 Vitest 的覆蓋率工具,理解了不同類型的覆蓋率指標,並知道如何合理地運用覆蓋率來改善測試品質。
記住:覆蓋率是手段而非目的。用覆蓋率來指導測試策略,而不是盲目追求數字!明天我們將總結前十天的學習,為更進階的測試技巧做準備。 🚀
挑戰作業:檢查你現有專案的測試覆蓋率,找出至少三個未被測試的函數,為它們補充測試案例。