Go's standard library includes everything you need for testing: the testing package plus the go test command.

Where tests live

  • Tests live in files named *_test.go next to the code they test.
  • They belong to the same package, or <pkg>_test for "black-box" tests that may only use the exported API.
  • They are not compiled into your normal binary.

A first test

Source strings.go (in examples/11_testing/):

package strs

func Reverse(s string) string {
    rs := []rune(s)
    for i, j := 0, len(rs)-1; i < j; i, j = i+1, j-1 {
        rs[i], rs[j] = rs[j], rs[i]
    }
    return string(rs)
}

Test strings_test.go:

package strs

import "testing"

func TestReverse(t *testing.T) {
    got := Reverse("hello")
    want := "olleh"
    if got != want {
        t.Errorf("Reverse(\"hello\") = %q, want %q", got, want)
    }
}

Run:

go test ./...
go test -run TestReverse ./part1-language/examples/11_testing
go test -v ./part1-language/examples/11_testing

Table-driven tests (idiomatic)

func TestReverseTable(t *testing.T) {
    cases := []struct {
        name, in, want string
    }{
        {"empty", "", ""},
        {"ascii", "abc", "cba"},
        {"unicode", "héllo", "olléh"},
        {"palindrome", "level", "level"},
    }
    for _, tc := range cases {
        t.Run(tc.name, func(t *testing.T) {
            if got := Reverse(tc.in); got != tc.want {
                t.Errorf("Reverse(%q) = %q, want %q", tc.in, got, tc.want)
            }
        })
    }
}

t.Run creates subtests that show up individually:

--- PASS: TestReverseTable (0.00s)
    --- PASS: TestReverseTable/empty (0.00s)
    --- PASS: TestReverseTable/ascii (0.00s)
    ...

You can run a single subtest:

go test -run TestReverseTable/unicode ./...

Helpers and t API

Method What it does
t.Error(args...)/t.Errorf(...) Mark failed, continue.
t.Fatal(args...)/t.Fatalf(...) Mark failed and stop this test/subtest.
t.Log(args...) / t.Logf(...) Print only when -v or test fails.
t.Skip(...) / t.Skipf(...) Skip the test.
t.Helper() Mark a function as a helper so failure lines point to caller.
t.Cleanup(fn) Register code to run when the test ends.
t.TempDir() Create a fresh temp dir; auto-removed after test.
t.Parallel() Run this test in parallel with others marked.

Benchmarks

func BenchmarkReverse(b *testing.B) {
    s := "hello, world"
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        Reverse(s)
    }
}

Run:

go test -bench=. -benchmem ./...

b.N is chosen by the runtime to give a stable measurement.

Examples (also act as docs)

A function named ExampleXxx is run as a test and its output is checked against the trailing comment:

func ExampleReverse() {
    fmt.Println(Reverse("abc"))
    // Output: cba
}

pkg.go.dev and go doc will display these alongside the function.

Fuzzing (Go 1.18+)

func FuzzReverse(f *testing.F) {
    f.Add("hello")
    f.Fuzz(func(t *testing.T, s string) {
        rev := Reverse(Reverse(s))
        if rev != s {
            t.Errorf("Reverse(Reverse(%q)) = %q", s, rev)
        }
    })
}

Run:

go test -fuzz=FuzzReverse -fuzztime=10s ./...

The fuzzer mutates f.Add seeds and saves any failing input to testdata/fuzz/... for regression.

Coverage

go test -cover ./...
go test -coverprofile=cover.out ./...
go tool cover -html=cover.out

Tips for good Go tests

  • Prefer table-driven tests; they scale and document behaviour.
  • Test the exported API, not internals (use <pkg>_test package).
  • Never share state between tests; use t.Cleanup to tear down.
  • Use t.Parallel() in CPU-bound tests, but be aware of shared globals.
  • Keep test assertions explicit; prefer if got != want { t.Errorf(...) } over heavyweight assertion libraries (community is split, but the standard library way is great).

Run the example tests

go test ./part1-language/examples/11_testing