Step 1 of 7 · Reading · ~3 min
Learn
Errors, Testing, and Performance
Testing and Benchmarking
You will write table-driven tests with subtests and cleanups, stub a dependency through a small interface, and read a benchmark's ns/op and allocs/op. Until now you checked exercises by running main and reading the output; go test does that checking for you.
Errorf and Fatalf
A test is a TestXxx(t *testing.T) function in a _test.go file. Say Slug(s string) string lowercases and joins words with -:
--- FAIL: TestSlug (0.00s)
slug_test.go:7: Slug("Go Rocks ") = "go--rocks-", want "go-rocks"
FAIL
Errorf records the failure and the test keeps going, so one run reports every broken check. Fatalf records it and stops that test only; use it when the next line would crash on the bad value, such as cfg.Port after a failed Parse. The real one ends its goroutine with runtime.Goexit, which runs deferred calls, then stops quietly:
deferred call ran
main continues
Start a shared assertion helper with t.Helper() so failures point at its caller.
Table-driven tests and subtests
One loop, one t.Run per case:
--- FAIL: TestSlug (0.00s)
--- FAIL: TestSlug/trailing_space (0.00s)
slug_test.go:12: Slug("go rocks ") = "go-rocks-", want "go-rocks"
FAIL
A subtest is named parent/child with spaces turned into underscores, and one failing child marks the parent FAIL. With t.Parallel() inside the closure, add tc := tc first (the loop-variable trap from the goroutines lesson).
Cleanup
t.Cleanup(fn) runs fn after the test and its subtests finish, last registered first, even after Fatalf. Unlike defer, it works from a helper:
TestMain(m *testing.M) wraps package-wide setup around m.Run(). (Cleanup is Go 1.14, newer than this grader's Go 1.13.)
Doubles, examples, benchmarks
Depend on a small interface and the test supplies a fake. To stub one method of a bigger interface, embed it:
Any other method panics on the nil. Assert with errors.Is(err, ErrDown) and reflect.DeepEqual(got, want).
An ExampleSlug() that prints and ends with // Output: go-rocks is run and compared by go test, so docs cannot drift.
A benchmark runs the work b.N times; b.ResetTimer() drops setup from the timing:
go test -bench=Slug -benchmem prints BenchmarkSlug-8 28744 41203 ns/op 9216 B/op 14 allocs/op: 41203 ns, 9216 bytes and 14 allocations per call.
Flags: -run 'TestSlug/trailing' selects a subtest, -v lists tests, -count=3 repeats past the cache, -race finds data races, -cover reports coverage. -fuzz (Go 1.18) feeds generated inputs and keeps failures in testdata/fuzz.
Common mistakes
Errorfbefore a dereference. The next line readscfg.Porton nil, and that panic buries the real message.Fatalfstops first.- Expecting cleanups in registration order. Registered
athenb, they runbthena. - Parallel subtests without
tc := tc. Every subtest checks the last case and all pass. - Setup inside the timed loop. Without
ResetTimer, ns/op includes building the input.
Your exercise
The grader cannot run go test, so you build a miniature T. The starter has a finished test and the printing; implement Errorf, Fatalf, Cleanup and Run. The likeliest slip is an aborted test that skips the cleanups it already registered.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…