Go Testing Basics
10 examples to get you started with Testing & Fuzzing - 7 basic and 3 intermediate.
Search across all documentation pages
10 examples to get you started with Testing & Fuzzing - 7 basic and 3 intermediate.
mkdir testing-demo && cd testing-demo && go mod init example.com/testing-demo.go test is built into the toolchain.go version
go test ./...A test is an ordinary function named TestXxx that receives *testing.T.
package mathutil
import "testing"
func Add(a, b int) int { return a + b }
func TestAdd(t *testing.T) {
if got := Add(2, 3); got != 5 {
t.Fatalf("Add(2,3) = %d, want 5", got)
}
}t.Fatalf marks the test failed and stops the current function._test.go and live beside production code.go test from the module root.Related: Go Testing Culture: Simple, Fast, Table-Driven - why Go tests look like this
go test accepts package patterns like production builds.
go test ./mathutil
go test -v ./...-v prints each test name as it runs../... walks all packages under the module.Related: Coverage, Race Detector & Golden Files - flags beyond plain
go test
Collect inputs and expectations in a slice, then loop.
func TestAddTable(t *testing.T) {
tests := []struct {
a, b, want int
}{
{1, 2, 3},
{0, 0, 0},
{-1, 1, 0},
}
for _, tc := range tests {
if got := Add(tc.a, tc.b); got != tc.want {
t.Fatalf("Add(%d,%d) = %d, want %d", tc.a, tc.b, got, tc.want)
}
}
}want, input, err).Related: Table-Driven Tests & Subtests -
t.Runand shared setup
Subtests name each case in failure output.
func TestAddSub(t *testing.T) {
for _, tc := range []struct {
name string
a, b, want int
}{
{"positives", 1, 2, 3},
{"zeros", 0, 0, 0},
} {
t.Run(tc.name, func(t *testing.T) {
if got := Add(tc.a, tc.b); got != tc.want {
t.Fatalf("got %d want %d", got, tc.want)
}
})
}
}go test -run TestAddSub/zeros.t.Parallel() for concurrent cases.name a distinct string per row.Related: Table-Driven Tests & Subtests - parallel tables and setup helpers
Mark helper functions so failures report the caller's line.
func assertEqual(t *testing.T, got, want int) {
t.Helper()
if got != want {
t.Fatalf("got %d want %d", got, want)
}
}
func TestWithHelper(t *testing.T) {
assertEqual(t, Add(1, 1), 2)
}t.Helper(), failures point inside the helper, not the test.if got != want.export_test.go or a testing subpackage for reuse.Error records failure and continues; Fatal stops the current test function.
func TestMultiCheck(t *testing.T) {
if Add(1, 1) != 2 {
t.Error("add failed")
}
if Add(2, 2) != 4 {
t.Fatal("second check failed")
}
t.Log("only runs if Fatal did not fire")
}Fatal when later assertions assume earlier state.Log output appears with -v or on failure.Error calls in one function.package foo_test imports the module as consumers do.
// in add_ext_test.go
package mathutil_test
import (
"testing"
"example.com/testing-demo/mathutil"
)
func TestAddExported(t *testing.T) {
if mathutil.Add(3, 4) != 7 {
t.Fatal("unexpected sum")
}
}package mathutil) can touch unexported helpers.Related: Examples as Executable Documentation - godoc examples in test files
net/http/httptest records responses without opening a real port.
import (
"fmt"
"net/http"
"net/http/httptest"
"testing"
)
func TestHelloHandler(t *testing.T) {
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "ok")
})
req := httptest.NewRequest(http.MethodGet, "/", nil)
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
if rec.Code != http.StatusOK || rec.Body.String() != "ok" {
t.Fatalf("got %d %q", rec.Code, rec.Body.String())
}
}NewRecorder captures status, headers, and body.Related: testify, httptest & Test Doubles - assertions and interface fakes
testify shortens comparison failures with readable diffs.
go get github.com/stretchr/testify@latestimport (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestWithTestify(t *testing.T) {
require.Equal(t, 4, Add(2, 2))
assert.NotEqual(t, 0, Add(1, 2))
}require stops the test on failure like t.Fatal.assert continues like t.Error.Related: testify, httptest & Test Doubles - mocks and suites
Benchmarks and -race extend the same go test command.
import "testing"
func BenchmarkAdd(b *testing.B) {
for b.Loop() {
Add(1, 2)
}
}go test -bench=. -benchmem ./mathutil
go test -race ./...b.Loop() (Go 1.24+) replaces manual b.N loops and resets timers correctly.-race slows tests but catches data races in goroutine code.Related: Benchmarks with testing.B & B.Loop - comparing implementations | Fuzz Testing with testing.F - seed corpora
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).
Reviewed by Chris St. John·Last updated Jul 19, 2026