Automated Testing
Go ships its test framework in the standard library: go test and the testing package, no runner to choose, no assertion DSL. Tables, doubles, benchmarks, fuzz targets and flake control all live here.
part ofGo (Golang)overview, primer and where to startread it →on this pageshowhide
explore
- Authoring and Fixtures33 questions
- Doubles and Seams17 questions
- Consumer-Side Interfaces5 questions
- Generated Mocks and Costs4 questions
- httptest Servers and Recorders4 questions
- Fake Clocks and Filesystems4 questions
- Run Control23 questions
- Result Reuse and -count4 questions
- Running Under -race5 questions
- Tagging Slow Suites5 questions
- TestMain Entry Point4 questions
- Coverage Profiles5 questions
- Benchmarks and Fuzzing17 questions
- Corpus Files and Minimization5 questions
- Compiler Elision and Sinks4 questions
- The b.N Loop4 questions
- Writing a Fuzz Target4 questions
- Removing Nondeterminism12 questions
- Deterministic Time with synctest4 questions
- Goroutine Leak Detection4 questions
- Diagnosing Flaky Tests4 questions
questions
102 · 5 sectionsHow do you make a Go type that calls time.Now() testable, and why is a now func() time.Time field the usual seam?
basics
~20 sStore the clock as a func() time.Time field that defaults to time.Now, and read the time through it. A test assigns a closure returning a fixed time.Date value, so results no longer depend on when the test runs.
How do you exercise an http.Handler directly in a Go test without binding a port?
basics
~20 sBuild the request with httptest.NewRequest and pass it, together with an httptest.NewRecorder, straight to the handler's ServeHTTP. The recorder captures status, headers and body in memory, so no port is bound and nothing is served.
How do you make a Go service that reads users from a database unit-testable without one?
basics
~20 sDeclare a small interface in the package that uses the dependency, naming only the one or two methods that code actually calls. Take it as a constructor parameter. In a _test.go file, pass a hand-written struct that implements those methods.
Go's `testing` package has no assertion API — how do you check a mock's recorded calls, and when is `t.Fatalf` wrong?
basics
~20 sYou compare with a plain if and report with t.Errorf, using the got/want convention: if got != want { t.Errorf("Update calls = %d, want %d", got, want) }. t.Errorf records the failure and keeps going; t.Fatalf stops the test and is valid only on the goroutine running the test function.
What does a `//go:generate` line above an interface do, and when does that command actually run?
basics
~20 sIt is an ordinary comment that the compiler ignores. Only an explicit go generate run scans source files for lines starting with //go:generate and executes the rest as a command in that package's directory. go build and go test never trigger it.
In `go test` output, what does the `(cached)` marker mean, and how do you force a real run?
basics
~10 sThe (cached) marker means go test found a stored successful result for that package and reprinted its output instead of running the test binary. Only passing runs are stored; add -count=1 to force execution.
How do you produce a `go test` coverage profile and view which lines were never executed?
basics
~20 sRun go test -coverprofile=cover.out ./... to write a coverage profile file, then go tool cover -func=cover.out for per-function percentages, or go tool cover -html=cover.out to open a page where every uncovered source line is highlighted.
What does go test -race do, and what happens to the run when it detects a race?
basics
~20 sgo test -race compiles the tests with the race detector, which watches memory accesses as they run. A conflict prints a WARNING: DATA RACE report naming both accesses with their goroutine stacks, fails that test, and makes go test exit non-zero.
What is func TestMain(m *testing.M), and when does go test call it instead of running tests?
basics
~20 sTestMain is an optional function a test package may declare. The test binary then calls it instead of running the tests directly, and the tests run only when it calls m.Run. It is the hook for package-wide setup and teardown.
How do you put a Go integration test file behind a build tag so it runs only on demand?
basics
~20 sPut //go:build integration on the first line of the _test.go file, followed by a blank line before the package clause. The file is then excluded from every ordinary go test run and compiled back in only by go test -tags=integration.
Where does `go test -fuzz` save an input that makes a fuzz target fail, and what replays it later?
basics
~20 sThe fuzzing engine writes the failing input into a new file under testdata/fuzz/<FuzzName>/ in the package's own directory. That file becomes a seed corpus entry, so every later plain go test run replays it, with no -fuzz flag needed.
What does a Go fuzz target `func FuzzParseQuery(f *testing.F)` do, and what are `f.Add` and `f.Fuzz` for?
basics
~20 sA Go fuzz target is a FuzzXxx function in a _test.go file that takes *testing.F. f.Add registers seed inputs; f.Fuzz takes the callback the engine runs, first on those seeds and then on mutated variations of them.
In a Go benchmark func BenchmarkX(b *testing.B), what is b.N and who sets its value?
basics
~20 sb.N is the iteration count the testing package hands a benchmark; you never set it. go test calls the whole function repeatedly with a growing b.N until the run reaches -benchtime, then reports elapsed time divided by b.N.
A Go benchmark that hashes a 1 MiB buffer reports 0.3 ns/op — what most likely happened?
basics
~20 sThe compiler deleted the work. A function with no side effects whose result is never used becomes dead code once it is inlined, so the loop measures only its own counter. Keep the result alive by storing it in a package-level variable.
How does the seed corpus under `testdata/fuzz` differ from the fuzzing corpus Go keeps in the build cache?
basics
~20 stestdata/fuzz is checked-in source: it travels with the repository and runs on every plain go test. The generated corpus lives in the build cache directory, is machine-local, is only used while fuzzing, and go clean -fuzzcache deletes it.
Why does a test asserting runtime.NumGoroutine() is unchanged fail intermittently?
basics
~20 sThe number is process-wide and momentary: a goroutine told to stop may not have run its last statement, other packages start background goroutines lazily, and parallel tests share the count. Poll to a deadline and compare stacks.
A worker-pool test sleeps 50ms before asserting and goes red in CI weekly — how do you prove the sleep is the cause?
basics
~20 sMake it fail on demand rather than reasoning about it: narrow with -run, repeat with -count, build with -race, and squeeze the parallelism the way CI does. Then vary the sleep constant — if the failure rate tracks it, the sleep is the synchronisation.
Why does re-running `go test` after a flaky failure print `(cached)`, and how do you force a real re-run?
basics
~20 sGo's test cache stores the result of a run that succeeded and replays it when nothing relevant changed, printing (cached) without executing anything. Add -count=1 to force a real run, or discard the stored results with go clean -testcache.
How can a Go test detect that the code it exercised left a goroutine still running?
basics
~20 sSample runtime.NumGoroutine() before the exercised code and again after it should have shut down; a higher number means something never exited. Dumping the runtime/pprof goroutine profile instead of counting also shows the leftover stacks, so you learn where.
What does testing/synctest's Test function give a Go test that real time.Sleep calls cannot?
basics
~20 ssynctest.Test runs a test inside a bubble with a fake clock, so time.Sleep, timers and tickers jump forward instantly instead of really waiting. A five-minute expiry can be tested in microseconds, with no slow-machine flakes.