Testing in Go

Table-driven tests, benchmarks, test helpers, mocking, and test coverage.

Intermediate 35 min read 🐹 Go

Testing Basics

Go has built-in testing — no frameworks needed. Create a file ending in _test.go and write functions starting with Test:

// calc.go
package calc

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

// 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)
    }
}
go test -v ./...
Output
=== RUN   TestAdd
--- PASS: TestAdd (0.00s)
PASS

Table-Driven Tests (Go Idiom)

func TestAdd(t *testing.T) {
    tests := []struct {
        name     string
        a, b     int
        expected int
    }{
        {"positive", 2, 3, 5},
        {"negative", -1, 1, 0},
        {"zeros", 0, 0, 0},
        {"large", 1000000, 1, 1000001},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got := Add(tt.a, tt.b)
            if got != tt.expected {
                t.Errorf("Add(%d, %d) = %d, want %d", tt.a, tt.b, got, tt.expected)
            }
        })
    }
}
Key Takeaway: Table-driven tests are the Go idiom. They're easy to read, easy to extend, and each case runs as a subtest with its own name. Always use this pattern.

Benchmarks

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

Practice Exercises

Medium Build a Mini Project

Combine concepts from this tutorial to build a small utility or tool.

Medium Debug Challenge

Introduce a bug in one of the code examples and practice finding and fixing it.

Hard Refactoring Exercise

Rewrite one example using a different approach and compare the tradeoffs.