Go Testing

Test Functions

Test files end in _test.go
// math_test.go
package math

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)
    }
}

Test functions start with Test and take *testing.T. The go test runner discovers them automatically.

Table-Driven Tests

The idiomatic Go test pattern
func TestParsePort(t *testing.T) {
    tests := []struct {
        name    string
        input   string
        want    int
        wantErr bool
    }{
        {"valid port", "8080", 8080, false},
        {"zero", "0", 0, true},
        {"negative", "-1", 0, true},
        {"too high", "70000", 0, true},
        {"not a number", "abc", 0, true},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got, err := ParsePort(tt.input)
            if (err != nil) != tt.wantErr {
                t.Fatalf("error = %v, wantErr %v", err, tt.wantErr)
            }
            if got != tt.want {
                t.Errorf("got %d, want %d", got, tt.want)
            }
        })
    }
}

t.Run creates named subtests — each can be run individually. t.Fatalf stops the subtest; t.Errorf reports and continues.

Running Tests

go test commands
go test                             # current package
go test -v                          # verbose
go test ./...                       # all packages recursively
go test -run TestParsePort          # by name
go test -run TestParsePort/valid    # specific subtest
go test -cover                      # coverage percentage
go test -coverprofile=c.out ./...   # coverage report
go tool cover -html=c.out           # view in browser
go test -race ./...                 # race detector
go test -bench=. -benchmem          # benchmarks

-race enables the race detector. Zero false positives, ~10x slowdown. Use it in CI.

Benchmarks

Performance measurement
func BenchmarkConcat(b *testing.B) {
    for i := 0; i < b.N; i++ {
        s := ""
        for j := 0; j < 100; j++ {
            s += "x"
        }
    }
}

func BenchmarkBuilder(b *testing.B) {
    for i := 0; i < b.N; i++ {
        var sb strings.Builder
        for j := 0; j < 100; j++ {
            sb.WriteString("x")
        }
        _ = sb.String()
    }
}
// BenchmarkConcat-8     123456   9876 ns/op   5432 B/op   99 allocs/op
// BenchmarkBuilder-8   1234567    987 ns/op    512 B/op    4 allocs/op

-benchmem shows allocations, which often matter more than raw speed.

Test Helpers

t.Helper and t.Cleanup
func assertEqual(t *testing.T, got, want any) {
    t.Helper() // error reports point to the caller
    if got != want {
        t.Errorf("got %v, want %v", got, want)
    }
}

func tempFile(t *testing.T, content string) string {
    t.Helper()
    f, err := os.CreateTemp("", "test-*")
    if err != nil {
        t.Fatal(err)
    }
    f.WriteString(content)
    f.Close()
    t.Cleanup(func() { os.Remove(f.Name()) })
    return f.Name()
}

t.Helper() makes error messages point to the calling test. t.Cleanup registers teardown that runs after the test completes.