Testing

Goテストフレームワーク単体テスト標準ライブラリTDDベンチマーク

Testing(Go標準ライブラリ)

概要

TestingはGo言語の標準ライブラリに含まれるテストフレームワークです。Goのgo testコマンドと連携して、単体テスト、ベンチマーク、サブテスト、カバレッジ測定など、包括的なテスト機能を提供します。シンプルで直感的なAPIと高性能な実行により、Go開発において必須のテストソリューションです。

詳細

主な特徴

  • 標準ライブラリ統合: Goの標準ライブラリとして提供され、外部依存なし
  • go testコマンド統合: Go toolchainと完全統合された実行環境
  • サブテスト機能: テストの階層化と並列実行をサポート
  • ベンチマーク機能: 内蔵されたパフォーマンス測定機能
  • カバレッジ分析: コードカバレッジ測定とレポート生成
  • テストフィクスチャ: セットアップとティアダウンの管理
  • 並列テスト: 高速なテスト実行のための並列化サポート
  • プロファイリング: CPU、メモリプロファイルの統合

アーキテクチャ

Testingパッケージは以下の核心コンポーネントで構成されています:

  • testing.T: 単体テスト用のテストハンドラー
  • testing.B: ベンチマーク用のハンドラー
  • testing.M: テストメイン関数の制御
  • testing.TB: テストとベンチマークの共通インターフェース

メリット・デメリット

メリット

  • 標準統合: Goエコシステムとの完全な統合
  • 高性能: 最適化されたテスト実行環境
  • シンプルAPI: 学習コストの低いインターフェース
  • 豊富な機能: 単体テストからベンチマークまで一元管理
  • ツールチェーン統合: IDEやCI/CDツールとの親和性
  • 並列実行: 効率的なマルチコアテスト実行

デメリット

  • 限定的な表現力: BDDスタイルやDSLは未サポート
  • アサーションライブラリなし: 基本的なエラーチェックのみ
  • モック機能: 標準ではモッキング機能は提供されない
  • テストデータ管理: 外部テストデータ管理機能は限定的

参考ページ

書き方の例

基本的なテスト

package main

import (
    "testing"
)

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

func TestAdd(t *testing.T) {
    result := Add(2, 3)
    expected := 5
    
    if result != expected {
        t.Errorf("Add(2, 3) = %d; want %d", result, expected)
    }
}

func TestAddNegatives(t *testing.T) {
    result := Add(-1, -2)
    expected := -3
    
    if result != expected {
        t.Errorf("Add(-1, -2) = %d; want %d", result, expected)
    }
}

テーブル駆動テスト

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

    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 TestConcurrent(t *testing.T) {
    t.Run("SubTest1", func(t *testing.T) {
        t.Parallel()
        // テストロジック
        time.Sleep(100 * time.Millisecond)
        t.Log("SubTest1 completed")
    })
    
    t.Run("SubTest2", func(t *testing.T) {
        t.Parallel()
        // テストロジック
        time.Sleep(100 * time.Millisecond)
        t.Log("SubTest2 completed")
    })
}

ベンチマーク

func BenchmarkAdd(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Add(42, 24)
    }
}

func BenchmarkAddWithSetup(b *testing.B) {
    // セットアップ(測定対象外)
    testData := make([]int, 1000)
    for i := range testData {
        testData[i] = i
    }
    
    b.ResetTimer() // タイマーリセット
    
    for i := 0; i < b.N; i++ {
        sum := 0
        for _, v := range testData {
            sum = Add(sum, v)
        }
    }
}

セットアップとティアダウン

func TestMain(m *testing.M) {
    // グローバルセットアップ
    setup()
    
    // テスト実行
    code := m.Run()
    
    // グローバルティアダウン
    teardown()
    
    os.Exit(code)
}

func setup() {
    fmt.Println("Setting up tests...")
}

func teardown() {
    fmt.Println("Tearing down tests...")
}

func TestWithCleanup(t *testing.T) {
    // テスト固有のセットアップ
    tempFile := createTempFile()
    
    // クリーンアップの登録
    t.Cleanup(func() {
        os.Remove(tempFile)
    })
    
    // テストロジック
    if _, err := os.Stat(tempFile); os.IsNotExist(err) {
        t.Error("Temp file should exist")
    }
}

エラーハンドリング

func TestErrorHandling(t *testing.T) {
    t.Run("Expected Error", func(t *testing.T) {
        _, err := riskyFunction()
        if err == nil {
            t.Error("Expected error but got none")
        }
    })
    
    t.Run("Specific Error", func(t *testing.T) {
        _, err := anotherRiskyFunction()
        if err != ErrSpecific {
            t.Errorf("Expected ErrSpecific, got %v", err)
        }
    })
    
    t.Run("Fatal Error", func(t *testing.T) {
        if criticalCondition() {
            t.Fatal("Critical condition failed, stopping test")
        }
        // t.Fatal後のコードは実行されない
    })
}

高度なテスト技法

func TestAdvanced(t *testing.T) {
    // テストのスキップ
    if testing.Short() {
        t.Skip("Skipping in short mode")
    }
    
    // ヘルパー関数の作成
    helper := func(t *testing.T, input int, expected int) {
        t.Helper() // スタックトレースでこの関数を除外
        result := complexFunction(input)
        if result != expected {
            t.Errorf("complexFunction(%d) = %d; want %d", 
                input, result, expected)
        }
    }
    
    helper(t, 5, 10)
    helper(t, 10, 20)
}

// メモリ効率のテスト
func TestMemoryUsage(t *testing.T) {
    var m1, m2 runtime.MemStats
    runtime.GC()
    runtime.ReadMemStats(&m1)
    
    // メモリを使用する処理
    data := make([]byte, 1024*1024) // 1MB
    _ = data
    
    runtime.ReadMemStats(&m2)
    allocated := m2.Alloc - m1.Alloc
    t.Logf("Allocated %d bytes", allocated)
}