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
// main.go
package main
func Add(a, b int) int {
return a + b
}
// 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:
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/failgo test -run Pattern, only run tests whose name matches the regexgo test -cover, report percentage of code covered by the tests that rango 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:
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.