iT邦幫忙

2025 iThome 鐵人賽

DAY 9
0
Modern Web

React TDD 實戰:用 Vitest 打造可靠的前端應用系列 第 9

Day 09 - 測試覆蓋率:你的測試真的夠完整嗎? 📊

  • 分享至 

  • xImage
  •  

今天的目標

還記得昨天我們學會了例外處理測試,確保程式在錯誤情況下的穩定運行嗎?今天要面對一個更深層的問題:「我們的測試到底覆蓋了多少程式碼?」

測試覆蓋率(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: 下階段預告

學習目標

今天結束後,你將學會:

  • 理解測試覆蓋率的概念和重要性
  • 掌握 Vitest 的覆蓋率工具使用
  • 學會分析覆蓋率報告
  • 理解不同類型的覆蓋率指標

📊 測試覆蓋率讓我們能夠量化測試完整性,發現潛在的測試盲點。

為什麼需要測試覆蓋率? 🎯

覆蓋率的好處:

  1. 發現測試盲點:找出未被測試的程式碼
  2. 提升測試品質:確保關鍵路徑都有測試
  3. 重構信心:更安全地進行程式碼重構
  4. 團隊溝通:提供客觀的測試完整性指標

Vitest 中的覆蓋率配置

安裝覆蓋率工具

更新 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
      }
    }
  }
})

覆蓋率類型詳解 📈

四大覆蓋率指標

  1. 語句覆蓋率(Statement Coverage)

    • 測量執行了多少程式碼語句
    • 最基本的覆蓋率類型
    • 例如:如果有 100 行程式碼,執行了 80 行,覆蓋率就是 80%
  2. 分支覆蓋率(Branch Coverage)

    • 測量執行了多少條件分支(if/else、switch)
    • 比語句覆蓋率更嚴格
    • 確保每個決策點的所有可能路徑都被測試
  3. 函數覆蓋率(Function Coverage)

    • 測量調用了多少函數
    • 找出從未被調用的「死代碼」
    • 幫助識別未使用的功能
  4. 行覆蓋率(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

這告訴我們:

  • 語句覆蓋率 75%:有 25% 的語句沒被執行
  • 分支覆蓋率 100%:所有條件分支都被測試了
  • 函數覆蓋率 66.67%:3 個函數中有 1 個沒被調用(multiply)
  • 行覆蓋率 75%:第 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%!

覆蓋率最佳實踐 ⚡

1. 不要追求 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
})

2. 關注關鍵業務邏輯

重點測試複雜的業務邏輯,而不是簡單的 getter/setter。

3. 使用覆蓋率指導重構

當覆蓋率很低時,可能表示函數太複雜,需要拆分成更小的函數。

今天學到什麼?

透過今天的學習,我們掌握了:

  1. 測試覆蓋率的概念:了解語句、分支、函數、行覆蓋率的差異
  2. Vitest 覆蓋率工具:配置和使用多種覆蓋率報告格式
  3. 覆蓋率分析技巧:識別測試盲點和改善策略
  4. 最佳實踐:平衡覆蓋率和測試品質,避免為覆蓋率而測試
  5. 實戰應用:處理複雜的分支邏輯覆蓋

測試覆蓋率讓我們能夠客觀衡量測試完整性,發現未測試的程式碼路徑,指導測試策略。記住,覆蓋率是品質的指標之一,但不是唯一指標!

重點回顧 📝

今天的關鍵要點:

  1. 覆蓋率類型

    • 語句覆蓋率:執行了多少語句
    • 分支覆蓋率:測試了多少條件路徑
    • 函數覆蓋率:調用了多少函數
    • 行覆蓋率:執行了多少行
  2. 工具配置

    • 使用 @vitest/coverage-v8
    • 設定合理的覆蓋率閾值
    • 產生多種報告格式
  3. 最佳實踐

    • 80-90% 是合理目標
    • 關注業務邏輯測試
    • 避免無意義的測試

明天預告 🔮

明天是第十天,我們將總結這十天學到的基礎測試概念,並為下一階段的學習做準備。你會學到如何將這些基礎概念整合運用,打造更完整的測試策略。

總結

今天我們深入探討了測試覆蓋率,這是 TDD 過程中重要的品質指標。我們學會了如何配置和使用 Vitest 的覆蓋率工具,理解了不同類型的覆蓋率指標,並知道如何合理地運用覆蓋率來改善測試品質。

記住:覆蓋率是手段而非目的。用覆蓋率來指導測試策略,而不是盲目追求數字!明天我們將總結前十天的學習,為更進階的測試技巧做準備。 🚀


挑戰作業:檢查你現有專案的測試覆蓋率,找出至少三個未被測試的函數,為它們補充測試案例。


上一篇
Day 08 - 例外處理測試 ⚠️
系列文
React TDD 實戰:用 Vitest 打造可靠的前端應用9
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言