Skip to content

Testing

Testing is built into the toolchain: no framework to install, just the testing package and the go test command.

Given math.go:

package math
func Add(a, b int) int {
return a + b
}

Create math_test.go in the same package:

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:

Terminal window
go test ./...

The _test.go suffix and Test-prefixed function name are the only conventions go test needs.

This is the idiomatic way to cover many cases without duplicating test logic:

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

Each t.Run becomes its own named subtest, so go test -run TestAdd/negative can target a single case.

func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
Add(2, 3)
}
}
Terminal window
go test -bench=. -benchmem

b.N is chosen automatically by the test runner to produce a stable measurement.

Terminal window
go test -cover ./...
go test -coverprofile=cover.out ./... && go tool cover -html=cover.out

The second command opens an HTML report highlighting exactly which lines your tests do and don’t exercise.