Testing and Benchmarking
Go's built-in testing package provides TestXxx functions, table-driven tests, BenchmarkXxx functions, and the testdata directory convention without external frameworks.
Go ships a test runner in the standard library. Test files end in _test.go and contain functions named TestXxx or BenchmarkXxx. Run tests with go test ./....
A test function accepts *testing.T. Call t.Error or t.Errorf to mark a failure without stopping the test. Call t.Fatal or t.Fatalf to mark a failure and stop immediately.
// math_test.go
package main
import "testing"
func add(a, b int) int { return a + b }
func TestAdd(t *testing.T) {
got := add(2, 3)
want := 5
if got != want {
t.Errorf("add(2, 3) = %d; want %d", got, want)
}
}Table-driven tests loop over a slice of cases. Each entry specifies inputs and the expected output. When a case fails, t.Errorf reports the case name so you can identify it without reading the data manually.
func TestAddTable(t *testing.T) {
cases := []struct {
name string
a, b int
want int
}{
{"positive", 2, 3, 5},
{"zero", 0, 0, 0},
{"negative", -1, -2, -3},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got := add(tc.a, tc.b)
if got != tc.want {
t.Errorf("got %d; want %d", got, tc.want)
}
})
}
}Benchmark functions accept *testing.B. The b.N field is set by the runner to the number of iterations needed to get a stable measurement. Run with go test -bench=. -benchmem to include allocation counts.
func BenchmarkAdd(b *testing.B) {
for range b.N {
add(2, 3)
}
}
// With expensive setup:
func BenchmarkAddSetup(b *testing.B) {
data := make([]int, 1000) // expensive setup
b.ResetTimer() // exclude setup from measurement
b.ReportAllocs() // surface allocation counts
for range b.N {
_ = data[0] + data[1]
}
}In production
Table-driven tests are idiomatic Go - they make adding cases trivial and the failure output shows which case failed by name. Use t.Helper() in assertion helper functions so failure line numbers point to the test that called the helper, not inside the helper itself. Benchmarks require b.ResetTimer() when setup is expensive, so setup time is excluded from the reported ns/op. b.ReportAllocs() surfaces allocation counts in the output, which is often more diagnostic than raw speed. Place fixture files under a testdata/ directory in the package - the go test tool changes to the package directory before running, so os.Open("testdata/input.json") works without path arithmetic. Use t.TempDir() for temporary directories in tests; it is automatically cleaned up when the test ends.
Enjoyed this? Get more essays on software craft delivered to your inbox.
Subscribe free