Testing
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)
}