TTuck

Unit testing is an important part of writing principled Go programs. The testing package provides the tools we need to write unit tests and the go test command runs tests. The tests for this example live next to it in main_test.go.

package main

import "fmt"

We'll be testing this simple implementation of an integer minimum. Typically, the code we're testing would be in a source file named something like intutils.go, and the test file for it would then be named intutils_test.go.

func IntMin(a, b int) int {
	if a < b {
		return a
	}
	return b
}

func main() {
	fmt.Println(IntMin(2, -2))
}
$ go run ./89_testing_and_benchmarking
-2
$ go test -count=1 -v ./89_testing_and_benchmarking
=== RUN   TestIntMinBasic
--- PASS: TestIntMinBasic (0.00s)
=== RUN   TestIntMinTableDriven
=== RUN   TestIntMinTableDriven/0,1
=== RUN   TestIntMinTableDriven/1,0
=== RUN   TestIntMinTableDriven/2,-2
=== RUN   TestIntMinTableDriven/0,-1
=== RUN   TestIntMinTableDriven/-1,0
--- PASS: TestIntMinTableDriven (0.00s)
    --- PASS: TestIntMinTableDriven/0,1 (0.00s)
    --- PASS: TestIntMinTableDriven/1,0 (0.00s)
    --- PASS: TestIntMinTableDriven/2,-2 (0.00s)
    --- PASS: TestIntMinTableDriven/0,-1 (0.00s)
    --- PASS: TestIntMinTableDriven/-1,0 (0.00s)
=== RUN   FuzzIntMin
=== RUN   FuzzIntMin/seed#0
--- PASS: FuzzIntMin (0.00s)
    --- PASS: FuzzIntMin/seed#0 (0.00s)
=== RUN   ExampleIntMin
--- PASS: ExampleIntMin (0.00s)
PASS
ok  	github.com/clivern/tuck/89_testing_and_benchmarking	0.003s
$ go test -count=1 -run=XXX -bench=. -benchtime=100000x ./89_testing_and_benchmarking
goos: linux
goarch: amd64
pkg: github.com/clivern/tuck/89_testing_and_benchmarking
cpu: AMD EPYC 9V74 80-Core Processor                
BenchmarkIntMin-4   	  100000	         0.9982 ns/op
PASS
ok  	github.com/clivern/tuck/89_testing_and_benchmarking	0.004s
$ go test -count=1 -cover ./89_testing_and_benchmarking
ok  	github.com/clivern/tuck/89_testing_and_benchmarking	0.003s	coverage: 75.0% of statements
$ go test -count=1 -race -run=TestIntMinBasic ./89_testing_and_benchmarking
ok  	github.com/clivern/tuck/89_testing_and_benchmarking	1.014s

89_testing_and_benchmarking/main_test.go

package main

import (
	"fmt"
	"testing"
)

A test is created by writing a function with a name beginning with Test.

func TestIntMinBasic(t *testing.T) {
	ans := IntMin(2, -2)
	if ans != -2 {

t.Error* will report test failures but continue executing the test. t.Fatal* will report test failures and stop the test immediately.

		t.Errorf("IntMin(2, -2) = %d; want -2", ans)
	}
}

Writing tests can be repetitive, so it's idiomatic to use a table-driven style, where test inputs and expected outputs are listed in a table and a single loop walks over them and performs the test logic.

func TestIntMinTableDriven(t *testing.T) {
	var tests = []struct {
		a, b int
		want int
	}{
		{0, 1, 0},
		{1, 0, 0},
		{2, -2, -2},
		{0, -1, -1},
		{-1, 0, -1},
	}

	for _, tt := range tests {

t.Run enables running "subtests", one for each table entry. These are shown separately when executing go test -v.

		testname := fmt.Sprintf("%d,%d", tt.a, tt.b)
		t.Run(testname, func(t *testing.T) {
			equalMin(t, tt.a, tt.b, tt.want)
		})
	}
}

Benchmark tests typically go in _test.go files and are named beginning with Benchmark. Any code that's required for the benchmark to run but should not be measured goes before the loop. b.Loop runs the body as many times as needed.

func BenchmarkIntMin(b *testing.B) {
	for b.Loop() {
		IntMin(1, 2)
	}
}

t.Helper marks equalMin as a helper, so a failure is reported at the call in the test rather than on the comparison below.

func equalMin(t *testing.T, a, b, want int) {
	t.Helper()
	if got := IntMin(a, b); got != want {
		t.Errorf("IntMin(%d, %d) = %d; want %d", a, b, got, want)
	}
}

Fuzz tests are named beginning with Fuzz. go test runs the seed inputs from f.Add. go test -fuzz keeps generating inputs until one fails or time runs out.

func FuzzIntMin(f *testing.F) {
	f.Add(1, -2)
	f.Fuzz(func(t *testing.T, a, b int) {
		got := IntMin(a, b)
		if got > a || got > b {
			t.Errorf("IntMin(%d, %d) = %d, larger than an argument", a, b, got)
		}
	})
}

An Example function documents usage, and go test checks its Output comment.

func ExampleIntMin() {
	fmt.Println(IntMin(3, 7))

Output: 3

}