0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Goで学ぶコンピューターサイエンスAdvent Calendar 2024

Day 23

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

Posted at

テストの基礎

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

Goのテストファイルは以下の規則に従います

  • ファイル名は *_test.go で終わる
  • テスト関数名は Test で始まる
  • テストパッケージ名は元のパッケージ名と同じか、{パッケージ名}_test とする
// 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 ユニットテスト

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

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

2.2 テーブル駆動テスト

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 ベンチマークテスト

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. テストのカバレッジ

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

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

デバッグ

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

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. 構造化ログの実装

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"},
	})
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?