Go Test Cheat Sheet for Everyday Testing Work in One Place
A Go testing cheat sheet: table tests, helpers, temp dirs, env, handlers, skips, parallel, benchmarks, fuzz and examples, with the commands that run each.

This is the page to keep open while writing tests. Each section is a pattern, a short reason, and the command that exercises it. Everything ran on Go 1.25.6 from the file below; the deeper articles are linked where a pattern has depth.
Tested with Go 1.25.6, macOS on Apple Silicon (arm64)
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
)
func double(n int) int { return n * 2 }
// 1. table test with named cases
func TestTable(t *testing.T) {
for _, c := range []struct {
name string
in, want int
}{{"zero", 0, 0}, {"pos", 2, 4}, {"neg", -3, -6}} {
t.Run(c.name, func(t *testing.T) {
if got := double(c.in); got != c.want {
t.Errorf("double(%d) = %d, want %d", c.in, got, c.want)
}
})
}
}
// 2. helper: failures point at the caller
func mustDouble(t *testing.T, in, want int) {
t.Helper()
if got := double(in); got != want {
t.Fatalf("double(%d) = %d, want %d", in, got, want)
}
}
func TestHelper(t *testing.T) { mustDouble(t, 4, 8) }
// 3. temp dir, cleanup, env
func TestTempAndEnv(t *testing.T) {
dir := t.TempDir() // removed automatically
path := filepath.Join(dir, "note.txt")
if err := os.WriteFile(path, []byte("hi"), 0o600); err != nil {
t.Fatal(err)
}
t.Setenv("APP_MODE", "test") // restored automatically
t.Cleanup(func() { t.Logf("cleanup runs last, file existed: %v", fileExists(path)) })
t.Logf("APP_MODE=%s", os.Getenv("APP_MODE"))
}
func fileExists(p string) bool { _, err := os.Stat(p); return err == nil }
// 4. HTTP handler without a server
func TestHandler(t *testing.T) {
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusTeapot) })
rec := httptest.NewRecorder()
h.ServeHTTP(rec, httptest.NewRequest("GET", "/", nil))
t.Logf("status=%d", rec.Code)
}
// 5. skip
func TestSkipShort(t *testing.T) {
if testing.Short() {
t.Skip("slow")
}
t.Log("full mode")
}
// 6. parallel subtests
func TestParallel(t *testing.T) {
for _, n := range []int{1, 2, 3} {
t.Run("n", func(t *testing.T) {
t.Parallel()
if double(n) != n+n {
t.Fail()
}
})
}
}
// 7. benchmark and 8. fuzz and 9. example
func BenchmarkDouble(b *testing.B) {
for b.Loop() {
double(21)
}
}
func FuzzDouble(f *testing.F) {
f.Add(3)
f.Fuzz(func(t *testing.T, n int) {
if double(n) != n+n {
t.Fatal("not additive")
}
})
}
func Example_double() {
fmt.Println(double(4))
// Output: 8
}
func main() {}G='s/ \([0-9.]+s\)//; s#^ +[a-z_0-9]+\.go:[0-9]+: # #; s/\t[0-9.]+s$//'
echo '$ go test -v -short'; go test -count=1 -v -short . 2>&1 | grep -vE '^(=== |PASS)' | sed -E "$G" | head -30
echo; echo '$ go test -run "TestTable/(pos|neg)" -v'; go test -count=1 -run 'TestTable/(pos|neg)' -v . 2>&1 | grep -E '^\s*---' | sed -E "$G"
echo; echo '$ go test -run "^$" -bench Double -benchtime 100x'; go test -count=1 -run '^$' -bench Double -benchtime 100x . 2>&1 | grep -E '^Benchmark' | sed -E 's/[[:space:]]+[0-9.]+ ns\/op.*/ ... ns\/op/'
echo; echo '$ go test -run "^$" -fuzz FuzzDouble -fuzztime 200x'; go test -count=1 -run '^$' -fuzz FuzzDouble -fuzztime 200x . 2>&1 | grep -E 'PASS|ok' | sed -E 's/\t[0-9.]+s//' | head -2$ go test -v -short
--- PASS: TestTable
--- PASS: TestTable/zero
--- PASS: TestTable/pos
--- PASS: TestTable/neg
--- PASS: TestHelper
APP_MODE=test
cleanup runs last, file existed: true
--- PASS: TestTempAndEnv
status=418
--- PASS: TestHandler
slow
--- SKIP: TestSkipShort
--- PASS: TestParallel
--- PASS: TestParallel/n
--- PASS: TestParallel/n#01
--- PASS: TestParallel/n#02
--- PASS: FuzzDouble
--- PASS: FuzzDouble/seed#0
--- PASS: Example_double
ok example.test/lab
$ go test -run "TestTable/(pos|neg)" -v
--- PASS: TestTable
--- PASS: TestTable/pos
--- PASS: TestTable/neg
$ go test -run "^$" -bench Double -benchtime 100x
BenchmarkDouble-10 100 ... ns/op
$ go test -run "^$" -fuzz FuzzDouble -fuzztime 200x
PASS
ok example.test/labThe patterns
| I want to | Write | Notes |
|---|---|---|
| Test many inputs | A slice of named cases and t.Run(name, ...) | The name is the failure label. See table tests |
| Share an assertion | A function taking *testing.T that calls t.Helper() first | Failures point at the caller's line |
| Get a scratch directory | t.TempDir() | Removed when the test ends |
| Set an environment variable | t.Setenv(k, v) | Restored after; panics in parallel tests |
| Change directory | t.Chdir(dir) | Go 1.24; restored after; not for parallel tests |
| Run code at the end | t.Cleanup(func(){...}) | Last in, first out |
| Test a handler | httptest.NewRequest and httptest.NewRecorder | No port. See the recorder article |
| Skip a slow test | if testing.Short() { t.Skip("reason") } | Run with -short to skip |
| Run subtests together | t.Parallel() inside each t.Run | Cases must not share state |
| Measure speed | func BenchmarkX(b *testing.B) with for b.Loop() | See benchmarks |
| Find odd inputs | func FuzzX(f *testing.F) with f.Add seeds | See fuzzing |
| Document and check | func ExampleX() ending in // Output: | See example tests |
| Control time | A clock parameter, or synctest.Test | See clock tests |
| Fail and stop | t.Fatalf | Only from the test goroutine |
| Fail and continue | t.Errorf | Use in loops and goroutines |
| Log for failures | t.Logf | Shown on failure or with -v |
What the run shows
Table test. TestTable produced three subtests named zero, pos and neg. Descriptive names make a failing row readable: TestTable/neg.
Helper and cleanup. TestTempAndEnv set APP_MODE=test, wrote a file into t.TempDir() and registered a cleanup. The cleanup logged file existed: true. That order is the rule: cleanups run before the temporary directory is removed, so a cleanup can still use files in it.
Handler. The recorder returned status=418 for a handler that writes the teapot status. No server was started.
Skip. With -short, TestSkipShort printed its reason and --- SKIP. Without the flag it would log full mode.
Parallel. Three subtests with the same name n were numbered n, n#01 and n#02. Go makes duplicate subtest names unique this way, which is a reason to give cases distinct names.
Fuzz as a test. Without -fuzz, FuzzDouble ran its seed once as FuzzDouble/seed#0. A fuzz target is a table test until you ask it to fuzz.
Example. Example_double printed 8 and matched its // Output: comment.
Selecting what runs
| Goal | Command |
|---|---|
| One test | go test -run '^TestTable$' |
| Some subtests | go test -run 'TestTable/(pos or neg)', written as a regular expression with alternation |
| Everything but some | go test -skip 'Slow' |
| List without running | go test -list . |
| Verbose | go test -v |
| No cache | go test -count=1 |
| Short mode | go test -short |
| Race detector | go test -race |
| Coverage | go test -cover -coverprofile=c.out |
| One benchmark | go test -run '^$' -bench Double -benchtime 100x |
| Fuzz for a while | go test -run '^$' -fuzz FuzzDouble -fuzztime 30s |
| Machine-readable | go test -json |
| Fail fast | go test -failfast |
The second row is a regular expression with an alternation of the two subtest names; the lab writes it with the vertical bar, quoted so the shell leaves it alone. The full flag list with notes is in the flags reference.
Habits worth keeping
- Name every case, and make the name say what is special about it.
- Put the input in the failure message:
double(%d) = %d, want %d. - Use
t.Fatalfwhen the rest of the test is meaningless after a failure,t.Errorfwhen later checks are still informative. - Keep the code under test out of the test's own logic; call it, compare, report.
- Let the framework clean up:
t.TempDir,t.Setenv,t.Cleanup,t.Context. - Run
-raceand-shufflein CI, because both turn silent shared-state bugs into failures you can read. - Keep slow and external tests behind a tag; see the build tag article.
Mistakes this sheet is meant to prevent
- Anchoring.
-run TestTablealso matchesTestTableSlow. Use^TestTable$when you mean one. - Forgetting the cache. A passing result may be replayed; add
-count=1when the test touches the outside world. See the cache article. - Sharing a loop variable. Fine since Go 1.22 in modules that declare it; copy it first on older ones.
- Calling
t.Fatalin a goroutine. Uset.Errorand signal the test goroutine;go vetreports the mistake. - Asserting nothing. A test that cannot fail is worse than no test, as the mistakes roundup shows.
Where to go next
The patterns above are the base. For the surrounding practice, the testing guide maps the topics, and the Go documentation for the testing package is the full reference. The go test command documents the flags.


