Writing Tests with go test
Test files and naming conventions
Section titled “Test files and naming conventions”Go’s test runner discovers tests through two rules enforced by the toolchain:
- File name ends in
_test.go— the compiler excludes these files from normal builds. - Function name starts with
Testfollowed by a capital letter —TestAdd,TestParseURL, etc.
A minimal test file for a math.go source file lives in the same package and directory:
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) }}Run it with:
go test ./... # run all tests in all packagesgo test -v ./... # verbose: print each test name and PASS/FAILgo test -run TestAdd # run only tests whose name matches the regext.Errorf vs t.Fatalf
Section titled “t.Errorf vs t.Fatalf”Both record a failure, but they differ in whether the test continues:
| Function | Marks failure | Stops test? |
|---|---|---|
t.Errorf | Yes | No — test continues |
t.Fatalf | Yes | Yes — test stops immediately |
Use t.Fatalf when a later assertion would panic if the earlier one failed (e.g., dereferencing a pointer that you just checked is nil).
func TestDivide(t *testing.T) { result, err := Divide(10, 0) if err == nil { t.Fatalf("expected an error for division by zero, got nil") } _ = result}Table-driven tests with t.Run
Section titled “Table-driven tests with t.Run”The idiomatic Go pattern for testing many inputs against the same function is a table-driven test: a slice of struct cases iterated with t.Run. Each sub-test gets its own name, failure message, and — with -v — its own line in the output.
package main
import "testing"
func TestFibonacci(t *testing.T) { cases := []struct { name string n int want int }{ {"fib(0)", 0, 0}, {"fib(1)", 1, 1}, {"fib(5)", 5, 5}, {"fib(10)", 10, 55}, }
for _, tc := range cases { tc := tc // capture range variable (pre-Go 1.22) t.Run(tc.name, func(t *testing.T) { got := Fibonacci(tc.n) if got != tc.want { t.Errorf("Fibonacci(%d) = %d; want %d", tc.n, got, tc.want) } }) }}Run with -v to see each sub-test:
go test -v -run TestFibonacci ./...# --- PASS: TestFibonacci (0.00s)# --- PASS: TestFibonacci/fib(0) (0.00s)# --- PASS: TestFibonacci/fib(1) (0.00s)# --- PASS: TestFibonacci/fib(5) (0.00s)# --- PASS: TestFibonacci/fib(10) (0.00s)The race detector
Section titled “The race detector”Add -race to catch data races at test time — this is mandatory in CI:
go test -race -coverprofile=coverage.out -covermode=atomic ./...The -covermode=atomic flag is required alongside -race for accurate coverage counts because the race detector rewrites memory accesses.