← Go, End to End

Go, End to End

Testing in Go

Testing is built into the toolchain. There's no separate framework to install to get started, just a convention: a file named foo_test.go next to foo.go, containing functions named TestXxx(t *testing.T).

A first test

go
// main.go
package main

func Add(a, b int) int {
	return a + b
}
go
// main_test.go
package main

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 in the package directory. t.Errorf marks the test as failed but lets it keep running (useful for reporting multiple issues in one test); t.Fatalf fails and stops immediately, for when continuing wouldn't make sense (e.g. a setup step failed and nothing after it can work).

Table-driven tests

The dominant idiom for testing several input/output pairs against the same logic is a slice of test cases run through a loop, using t.Run to give each case its own named subtest:

go · live, editable, runnable

This scales far better than one function per case: adding a new case is one line in the slice, and t.Run's subtests show up individually in go test -v output and can be filtered with -run TestAddTable/zeros.

Flags worth knowing

  • go test -v, verbose output, shows every subtest name and pass/fail
  • go test -run Pattern, only run tests whose name matches the regex
  • go test -cover, report percentage of code covered by the tests that ran
  • go test -race, run under the race detector (essential for anything concurrent, see the sync/data races chapter)

Benchmarks

Benchmarks live in the same test files, named BenchmarkXxx(b *testing.B), and run with go test -bench=.. The framework calls your function b.N times, adjusting N until the measurement is stable, so you don't hand-tune iteration counts yourself:

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

Beyond the standard library

The testing package alone is genuinely enough for most Go codebases. Third-party libraries like testify add fluent assertions (assert.Equal(t, want, got)) and mocking helpers that plenty of teams like, but they're a preference layered on top, not something the language or ecosystem requires you to reach for.

Try it yourself: write a table-driven test for a small function that returns whether a number is prime, including at least one edge case (0, 1, or a negative number) in the table.