Skip to content
Lesson 6 of 7

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 -:

go
--- 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:

go
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:

go
--- 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:

go

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:

go

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

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

  • Errorf before a dereference. The next line reads cfg.Port on nil, and that panic buries the real message. Fatalf stops first.
  • Expecting cleanups in registration order. Registered a then b, they run b then a.
  • 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.

Up nextPerformance and Escape AnalysisErrors, Testing, and Performance

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…