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.