1. go
  2. /testing

Testing in Go: A Comprehensive Guide

Go provides a robust testing framework in its standard library through the testing package. This guide covers everything you need to know about testing Go applications effectively.

Testing Fundamentals

Go's testing philosophy emphasizes simplicity and readability. Tests are:

  • Written in the same package as the code being tested
  • Stored in files with names ending in _test.go
  • Functions that start with Test
  • Take a single parameter of type *testing.T

Types of Tests

Unit Tests

Basic unit test structure:

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

Table-Driven Tests

Efficient way to test multiple scenarios:

func TestMultiply(t *testing.T) {
    tests := []struct {
        name     string
        x, y     int
        expected int
    }{
        {"positive numbers", 2, 3, 6},
        {"zero", 0, 5, 0},
        {"negative", -2, 3, -6},
    }
    
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            result := Multiply(tt.x, tt.y)
            if result != tt.expected {
                t.Errorf("Multiply(%d, %d) = %d; want %d",
                    tt.x, tt.y, result, tt.expected)
            }
        })
    }
}

Benchmarks

Performance testing with benchmarks:

func BenchmarkFibonacci(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Fibonacci(20)
    }
}

Testing Features

  1. Test Coverage

    • Run tests with coverage: go test -cover
    • Generate coverage profile: go test -coverprofile=coverage.out
    • View coverage report: go tool cover -html=coverage.out
  2. Test Flags

    • -v: Verbose output
    • -run: Run specific tests
    • -bench: Run benchmarks
    • -race: Enable race detector
  3. Testing Tools

    • go test: Run tests
    • go vet: Static analysis
    • golangci-lint: Comprehensive linting
    • gotests: Generate tests automatically

Best Practices

  1. Test Organization

    // calculator_test.go
    package calculator
    
    import "testing"
    
    func TestCalculator(t *testing.T) {
        t.Run("addition", testAddition)
        t.Run("subtraction", testSubtraction)
    }
    
    func testAddition(t *testing.T) {
        // Test cases
    }
    
    func testSubtraction(t *testing.T) {
        // Test cases
    }
    
  2. Test Helpers

    func setupTestCase(t *testing.T) func() {
        t.Helper()
        // Setup code
        return func() {
            // Cleanup code
        }
    }
    
  3. Parallel Testing

    func TestParallel(t *testing.T) {
        t.Parallel()
        // Test code
    }
    

Advanced Topics

  1. Integration Testing

    • Testing with external dependencies
    • Database integration
    • API testing
  2. Mocking

    • Interface-based mocking
    • Using testing doubles
    • Mock generation tools
  3. Fuzzing

    func FuzzReverse(f *testing.F) {
        f.Add("hello")
        f.Fuzz(func(t *testing.T, orig string) {
            rev := Reverse(orig)
            doubleRev := Reverse(rev)
            if orig != doubleRev {
                t.Errorf("Reverse(Reverse(%q)) = %q; want %q",
                    orig, doubleRev, orig)
            }
        })
    }
    

Common Testing Patterns

  1. Setup and Teardown

    func TestMain(m *testing.M) {
        // Setup
        code := m.Run()
        // Teardown
        os.Exit(code)
    }
    
  2. Test Fixtures

    func loadTestData(t *testing.T) []byte {
        t.Helper()
        data, err := os.ReadFile("testdata/sample.json")
        if err != nil {
            t.Fatal(err)
        }
        return data
    }
    
  3. Golden Files

    func TestComplex(t *testing.T) {
        got := ComplexFunction()
        golden := "testdata/complex.golden"
        if *update {
            os.WriteFile(golden, got, 0644)
        }
        want, err := os.ReadFile(golden)
        if err != nil {
            t.Fatal(err)
        }
        if !bytes.Equal(got, want) {
            t.Errorf("result mismatch")
        }
    }
    

Next Steps