← thecodex.expert · The Codex Family of Knowledge
Go

Testing

No test framework to install — name a file _test.go, name a function TestXxx, and go test finds and runs it.

Go 1.26 go test, built in Last verified:
Where this bites you

A test kicks off a goroutine to check something asynchronously, then returns right away without waiting for it — the goroutine is still sleeping when the test function exits. The test itself reports PASS. Then, moments later, from inside a completely different, unrelated test that happens to still be running, the whole suite crashes with a panic that has nothing to do with that test at all.

Goasync_test.go
func TestAsync(t *testing.T) {
	go func() {
		time.Sleep(50 * time.Millisecond)
		t.Errorf("work failed") // goroutine outlives the test function
	}()
	// TestAsync returns immediately, before the goroutine finishes
}
// --- PASS: TestAsync (0.00s)      <- looks fine at first...
// panic: Fail in goroutine after TestAsync has completed
//
// goroutine 7 [running]:
// testing.(*common).Errorf(...)
// asynctest.TestAsync.func1()
//         async_test.go:11 +0x45
// FAIL
Canonical Definition

Go testing needs no third-party framework: any function named TestXxx (taking a single *testing.T parameter) inside a file named *_test.go is automatically discovered and run by go test. The same mechanism extends to benchmarks (BenchmarkXxx, taking *testing.B) and example tests that double as documentation.

A basic test

A test file lives alongside the code it tests, named xxx_test.go. It calls t.Errorf (or t.Fatalf to stop immediately) to report a failure — there's no assertion library built in, though many projects add one.

Gomath_test.go
package mathutil

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 with go test in the package directory, or go test ./... to test every package in a module.

This is also where the bug in the callout above comes from: *testing.T is only safe to call from the test's own goroutine while that test is still running. If a test launches a goroutine and returns before the goroutine finishes, any later call to t.Errorf from it happens after the test has already been torn down — which the testing package treats as a hard panic, not a quiet failure, and it can surface while a totally different test is running. The fix is to make the test actually wait for its own goroutine before returning:

Goasync_fix_test.go
func TestAsync(t *testing.T) {
	done := make(chan struct{})
	go func() {
		defer close(done)
		time.Sleep(50 * time.Millisecond)
		t.Log("work finished inside the test's lifetime")
	}()
	<-done // block until the goroutine is done before the test returns
}
// --- PASS: TestAsync (0.05s)

Table-driven tests

The idiomatic way to test many input/output cases without repeating code is a table-driven test: a slice of structs describing each case, looped over with t.Run to name and isolate each one.

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

Benchmarks

A function named BenchmarkXxx taking *testing.B and looping b.N times measures performance; go test -bench=. runs it and reports time per operation, with the framework automatically adjusting b.N until the measurement is statistically stable.

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

Sources

1
The Go Authors. testing package documentation, pkg.go.dev/testing.