【Go】test -coverでカバレッジを計測・可視化する実践ガイド


こんにちは！フリーランスエンジニアのこたろうです。
今回は、Goのテストカバレッジについて、実践的な知見を共有します。

## なぜカバレッジを計測するのか？

テストカバレッジとは、コードがテストによってどの程度カバーされているかを示す指標です。
これを計測する理由は：
- バグの早期発見
- コードの品質維持
- テスト漏れの防止

## 基本的な使い方

### 1. カバレッジの計測
```bash
# 基本的な実行方法
go test -cover

# 詳細な結果を出力
go test -cover -v

# カバレッジファイルの生成
go test -coverprofile=coverage.out
```

### 2. カバレッジレポートの可視化
```bash
# HTMLレポートの生成
go tool cover -html=coverage.out -o coverage.html

# ターミナルでの表示
go tool cover -func=coverage.out
```

## 実践的な例

### テスト対象のコード
```go
// calc/calc.go
package calc

func Add(a, b int) int {
    return a + b
}

func Subtract(a, b int) int {
    return a - b
}

func Multiply(a, b int) int {
    return a * b
}

func Divide(a, b int) (int, error) {
    if b == 0 {
        return 0, fmt.Errorf("division by zero")
    }
    return a / b, nil
}
```

### テストコード
```go
// calc/calc_test.go
package calc

import (
    "testing"
)

func TestAdd(t *testing.T) {
    tests := []struct {
        name     string
        a, b     int
        expected int
    }{
        {"positive numbers", 2, 3, 5},
        {"negative numbers", -2, -3, -5},
        {"zero", 0, 0, 0},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            result := Add(tt.a, tt.b)
            if result != tt.expected {
                t.Errorf("Add(%d, %d) = %d; want %d", tt.a, tt.b, result, tt.expected)
            }
        })
    }
}

func TestDivide(t *testing.T) {
    tests := []struct {
        name        string
        a, b        int
        expected    int
        expectError bool
    }{
        {"normal division", 6, 2, 3, false},
        {"division by zero", 6, 0, 0, true},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            result, err := Divide(tt.a, tt.b)
            if tt.expectError && err == nil {
                t.Error("expected error but got none")
            }
            if !tt.expectError && err != nil {
                t.Errorf("unexpected error: %v", err)
            }
            if !tt.expectError && result != tt.expected {
                t.Errorf("Divide(%d, %d) = %d; want %d", tt.a, tt.b, result, tt.expected)
            }
        })
    }
}
```

## カバレッジレポートの読み方

### func単位のレポート
```bash
$ go tool cover -func=coverage.out
calc/calc.go:3:     Add         100.0%
calc/calc.go:7:     Subtract    0.0%
calc/calc.go:11:    Multiply    0.0%
calc/calc.go:15:    Divide      100.0%
total:              (statements) 66.7%
```

この結果から分かること：
- Add関数は100%カバー
- Subtract関数は未テスト
- Multiply関数は未テスト
- Divide関数は100%カバー
- 全体のカバレッジは66.7%

## カバレッジ向上のコツ

1. **テーブル駆動テストの活用**
```go
func TestMultiply(t *testing.T) {
    tests := []struct {
        name     string
        a, b     int
        expected int
    }{
        {"positive", 2, 3, 6},
        {"negative", -2, 3, -6},
        {"zero", 0, 5, 0},
    }
    // ...
}
```

2. **エッジケースのテスト**
```go
func TestDivide_EdgeCases(t *testing.T) {
    tests := []struct {
        name        string
        a, b        int
        expected    int
        expectError bool
    }{
        {"max int", math.MaxInt64, 1, math.MaxInt64, false},
        {"min int", math.MinInt64, 1, math.MinInt64, false},
        {"division by zero", 1, 0, 0, true},
    }
    // ...
}
```

3. **エラーケースのテスト**
```go
func TestDivide_Error(t *testing.T) {
    _, err := Divide(1, 0)
    if err == nil {
        t.Error("expected error for division by zero")
    }
}
```

## CI/CDでの活用

### GitHub Actionsでの例
```yaml
name: Go Test Coverage

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    
    - name: Set up Go
      uses: actions/setup-go@v2
      with:
        go-version: 1.19
    
    - name: Run Tests with Coverage
      run: |
        go test -coverprofile=coverage.out ./...
        go tool cover -func=coverage.out
    
    - name: Upload Coverage Report
      uses: actions/upload-artifact@v2
      with:
        name: coverage-report
        path: coverage.out
```

## まとめ

テストカバレッジを計測・可視化することで：
1. コードの品質を定量的に評価可能
2. テスト漏れを早期発見
3. リファクタリング時の安全性確保

ただし、カバレッジ100%を目指すことが必ずしも最適とは限りません。
コストとベネフィットを考慮しながら、適切なカバレッジ目標を設定することが重要です。