【Goで学ぶコンピューターサイエンス】テストとデバッグの基礎

## テストの基礎

### 1. テストファイルの構造

Goのテストファイルは以下の規則に従います
- ファイル名は `*_test.go` で終わる
- テスト関数名は `Test` で始まる
- テストパッケージ名は元のパッケージ名と同じか、`{パッケージ名}_test` とする

```go
// math/calculator.go
package math

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

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

// math/calculator_test.go
package math

import (
    "testing"
)

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

### 2. テストの種類

#### 2.1 ユニットテスト

基本的なユニットテストの例

```go
func TestMultiply(t *testing.T) {
    result := Multiply(4, 5)
    if result != 20 {
        t.Errorf("Multiply(4, 5) = %d; want 20", result)
    }
}
```

#### 2.2 テーブル駆動テスト

```go
func TestCalculator(t *testing.T) {
    tests := []struct {
        name        string
        operation   string
        a, b        int
        expected    int
        shouldError bool
    }{
        {
            name:        "simple addition",
            operation:   "add",
            a:          2,
            b:          3,
            expected:   5,
            shouldError: false,
        },
        {
            name:        "negative numbers addition",
            operation:   "add",
            a:          -2,
            b:          -3,
            expected:   -5,
            shouldError: false,
        },
        {
            name:        "multiplication with zero",
            operation:   "multiply",
            a:          5,
            b:          0,
            expected:   0,
            shouldError: false,
        },
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            var result int
            var err error

            switch tt.operation {
            case "add":
                result = Add(tt.a, tt.b)
            case "multiply":
                result = Multiply(tt.a, tt.b)
            }

            if (err != nil) != tt.shouldError {
                t.Errorf("error expectation failed: got %v", err)
                return
            }

            if result != tt.expected {
                t.Errorf("%s(%d, %d) = %d; want %d", 
                    tt.operation, tt.a, tt.b, result, tt.expected)
            }
        })
    }
}
```

#### 2.3 ベンチマークテスト

```go
func BenchmarkAdd(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Add(2, 3)
    }
}

func BenchmarkMultiply(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Multiply(4, 5)
    }
}
```

### 3. テストのカバレッジ

テストカバレッジを確認する方法

```bash
go test -cover ./...  # カバレッジの割合を表示
go test -coverprofile=coverage.out ./...  # カバレッジデータを生成
go tool cover -html=coverage.out  # ブラウザでカバレッジレポートを表示
```

## デバッグ

### 1. プリントデバッグの使用例

```go
package main

import "fmt"

type DebugLogger struct {
	enabled bool
}

func (d *DebugLogger) Printf(format string, args ...interface{}) {
	if d.enabled {
		fmt.Printf("[DEBUG] "+format+"\n", args...)
	}
}

var debug = &DebugLogger{enabled: true}

func ComplexCalculation(x int) int {
	debug.Printf("入力値: %d", x)

	intermediate := x * 2
	debug.Printf("中間結果: %d", intermediate)

	result := intermediate + 1
	debug.Printf("最終結果: %d", result)

	return result
}

func main() {
	result := ComplexCalculation(3)
	fmt.Println(result)
}

```

### 2. 構造化ログの実装

```go
package main

import (
	"fmt"
	"os"
	"time"
)

type Logger struct {
	output *os.File
}

type LogLevel int

const (
	INFO LogLevel = iota
	WARNING
	ERROR
)

type LogEntry struct {
	Level   LogLevel
	Message string
	Time    time.Time
	Data    map[string]interface{}
}

func getLevelString(level LogLevel) string {
	switch level {
	case INFO:
		return "INFO"
	case WARNING:
		return "WARNING"
	case ERROR:
		return "ERROR"
	default:
		return "UNKNOWN"
	}
}

func NewLogger() *Logger {
	return &Logger{
		output: os.Stdout,
	}
}

func (l *Logger) Log(entry LogEntry) {
	fmt.Fprintf(l.output, "[%s] %s: %s %v\n",
		entry.Time.Format(time.RFC3339),
		getLevelString(entry.Level),
		entry.Message,
		entry.Data,
	)
}

func main() {
	logger := NewLogger()

	logger.Log(LogEntry{
		Level:   INFO,
		Message: "This is an informational message",
		Time:    time.Now(),
		Data:    map[string]interface{}{"key": "value"},
	})

	logger.Log(LogEntry{
		Level:   WARNING,
		Message: "This is a warning message",
		Time:    time.Now(),
		Data:    map[string]interface{}{"key": "value"},
	})

	logger.Log(LogEntry{
		Level:   ERROR,
		Message: "This is an error message",
		Time:    time.Now(),
		Data:    map[string]interface{}{"key": "value"},
	})
}
```