Go Testing

Basic Testing

Creating and running tests:

// In file calc_test.go
package calc

import "testing"

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

// Run tests
// go test
// go test ./...         # Test all packages
// go test -v            # Verbose output
// go test -run TestAdd  # Run specific test

Test Tables

Testing multiple cases:

func TestAdd(t *testing.T) {
    tests := []struct {
        name     string
        a, b     int
        expected int
    }{
        {"positive", 2, 3, 5},
        {"negative", -1, -2, -3},
        {"mixed", -1, 5, 4},
    }
    
    for _, tc := range tests {
        t.Run(tc.name, func(t *testing.T) {
            got := Add(tc.a, tc.b)
            if got != tc.expected {
                t.Errorf("Add(%d, %d) = %d; want %d", 
                    tc.a, tc.b, got, tc.expected)
            }
        })
    }
}

Setup and Teardown

Managing test resources:

func TestMain(m *testing.M) {
    // Setup code
    setupDatabase()
    
    // Run tests
    code := m.Run()
    
    // Teardown code
    cleanupDatabase()
    
    // Exit with code from tests
    os.Exit(code)
}

func setupTest(t *testing.T) func() {
    // Setup for an individual test
    t.Log("Setting up test")
    
    // Return teardown function
    return func() {
        t.Log("Tearing down test")
    }
}

func TestSomething(t *testing.T) {
    teardown := setupTest(t)
    defer teardown()
    
    // Test code here
}

Subtests

Organizing tests hierarchically:

func TestUserAPI(t *testing.T) {
    t.Run("Create", func(t *testing.T) {
        // Test user creation
    })
    
    t.Run("Read", func(t *testing.T) {
        // Test user retrieval
    })
    
    t.Run("Update", func(t *testing.T) {
        // Test user update
    })
    
    t.Run("Delete", func(t *testing.T) {
        // Test user deletion
    })
}

// Run specific subtest
// go test -run TestUserAPI/Create

Benchmarking

Measuring performance:

func BenchmarkAdd(b *testing.B) {
    // Reset timer if doing setup
    b.ResetTimer()
    
    for i := 0; i < b.N; i++ {
        Add(2, 3)
    }
}

// Run benchmarks
// go test -bench=.
// go test -bench=Add
// go test -bench=. -benchmem  // Include memory allocations

Parallel Testing

Running tests concurrently:

func TestParallel(t *testing.T) {
    t.Parallel() // Mark test as parallel-capable
    
    // Test code here
}

// Run tests with limited parallelism
// go test -parallel 4

Test Helpers

Creating reusable test utilities:

// Helper function
func assertDeepEqual(t *testing.T, got, want interface{}) {
    t.Helper() // Mark as helper (improves error reporting)
    
    if !reflect.DeepEqual(got, want) {
        t.Errorf("Got %v, want %v", got, want)
    }
}

func TestWithHelper(t *testing.T) {
    got := []int{1, 2, 3}
    want := []int{1, 2, 3}
    assertDeepEqual(t, got, want)
}

Mocks and Fakes

Testing with dependencies:

// Interface for dependency
type DataStore interface {
    GetUser(id string) (User, error)
}

// Mock implementation
type MockDataStore struct {
    Users map[string]User
}

func (m *MockDataStore) GetUser(id string) (User, error) {
    user, exists := m.Users[id]
    if !exists {
        return User{}, errors.New("user not found")
    }
    return user, nil
}

// Testing with mock
func TestUserService(t *testing.T) {
    mockStore := &MockDataStore{
        Users: map[string]User{
            "123": {ID: "123", Name: "Bob"},
        },
    }
    
    service := NewUserService(mockStore)
    user, err := service.GetUserByID("123")
    
    if err != nil {
        t.Fatalf("Expected no error, got %v", err)
    }
    
    if user.Name != "Bob" {
        t.Errorf("Expected name 'Bob', got '%s'", user.Name)
    }
}

Coverage

Measuring test coverage:

# Run tests with coverage
go test -cover

# Generate coverage profile
go test -coverprofile=coverage.out

# View coverage in browser
go tool cover -html=coverage.out

# Check coverage percentage
go tool cover -func=coverage.out

Fuzzing (Go 1.18+)

Generate random inputs for tests:

```go func FuzzAdd(f *testing.F) { // Provide seed corpus f.Add(1, 2) f.Add(-1, -2)

// Fuzz test function
f.Fuzz(func(t *testing.T, a, b int) {
    result := Add(a, b)
    
    // Verify properties that should always hold
    if a > 0 && b > 0 && result <= 0 {
        t.Errorf("Add(%d, %d) = %d, expected positive", a, b, result)
    }
    
    if a == 0 && result != b {
        t.Errorf("Add(0, %d) = %d, expected %d", b, result, b)
    }
}) }

// Run fuzz tests // go test -fuzz=Fuzz