テストの基礎
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"},
})
}