# Go Testing — Derived Application > Applies Atelier's domain principles to Go testing specifically. > Derives from `domains/` docs; introduces no new P-rules (D-063). > See `languages/go.md` for the language first-principles stub. ## Table-Driven Tests (Testing P1 Tests as Specification, C2 Clarity) - **Table-driven is the Go idiom:** `cases := []struct{ name string; in X; want Y }{...}`; loop with `t.Run(c.name, ...)`. Each case is a subtest with its own name and failure output. - **Test names read as a spec:** `{"rejects empty email", ...}`, `{"returns persisted id", ...}`. A reader understands the unit from the subtest names (Testing P1). - **No `if got != want { t.Fatal() }` shared across cases:** each case asserts independently; a failure in case 3 does not skip cases 4 and 5. - **`t.Run` enables `-run` filtering:** `go test -run TestCreateUser/rejects_empty_email` runs one case. Essential for debugging a single failure. ```go func TestCreateUser(t *testing.T) { cases := []struct { name string email string wantErr bool }{ {"rejects empty email", "", true}, {"rejects missing @", "no-at-sign", true}, {"accepts valid email", "a@b.co", false}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { _, err := CreateUser(c.email) if (err != nil) != c.wantErr { t.Fatalf("err=%v, wantErr=%v", err, c.wantErr) } }) } } ``` ## t.Parallel for Independence (Testing P2 Independence, Concurrency P10 Test for Race Conditions) - **`t.Parallel()` for independent subtests:** each subtest opts in; the runner executes them concurrently. A test that fails under `Parallel` has hidden state (Testing P2 Independence). - **Capture loop variables:** `c := c` inside the loop, or rely on Go 1.22+ per-iteration scoping. A parallel subtest sharing `c` races on the last value. - **Applies `concurrency/P10` (test for races):** parallel tests are the first line of race detection; combine with `-race` for the full safety net. ```go for _, c := range cases { c := c // capture for parallel t.Run(c.name, func(t *testing.T) { t.Parallel() _, err := CreateUser(c.email) if (err != nil) != c.wantErr { t.Fatalf("err=%v, wantErr=%v", err, c.wantErr) } }) } ``` ## t.Cleanup for Teardown (Testing P3 Determinism, Testing P2 Independence) - **`t.Cleanup(func() { ... })` for teardown:** runs in LIFO order after the test (and its subtests) complete. Replaces `defer` in a helper that does not know when the test ends. - **Per-test state, not shared:** a `setup(t)` helper creates resources and registers cleanup; each test gets its own. A package-level `var` shared across tests is order coupling. - **`t.TempDir()` for filesystem tests:** creates a unique temp dir and cleans up automatically. No manual `os.RemoveAll` and no cross-test contamination. - **Applies `Testing P3` (determinism):** cleanup is tied to the test lifecycle, not a global teardown that may run before or after depending on order. ```go func setupStore(t *testing.T) *Store { t.Parallel() dir := t.TempDir() // auto-cleaned s, err := OpenStore(filepath.Join(dir, "db")) if err != nil { t.Fatal(err) } t.Cleanup(func() { s.Close() }) return s } ``` ## Race Detector (Testing P9 Edge Case Coverage, Concurrency P6 No Silent Races) - **`go test -race` in CI, always:** see `go-tooling.md`. The detector is the enforcement of `concurrency/P6`. - **Tests must exercise the concurrent path:** a serial test of a `Mutex`-protected map finds no race. Write tests with N goroutines. - **`-count=1` to disable result caching:** by default, Go caches passing tests. `-count=1` forces re-run; combine with `-race` and parallelism to surface heisenbugs. - **Applies `Testing P9` (edge case coverage):** the race detector is the edge-case tool for concurrency — it finds the inputs the test author forgot to write. ```bash # CI gate go test -race -count=1 ./... ``` ## Time and Determinism (Testing P3 Determinism, Testing P9 Edge Case Coverage) - **No `time.Now()` in code under test:** inject a `Clock` interface. In tests, a fake clock advances deterministically. - **`time.Sleep` in tests is a smell:** a sleep waits for a real timer, flaky under load. Use a channel or `Eventually`-style polling with a timeout. - **`t.Deadline()` aware helpers:** a helper that may take long checks `t.Deadline()` and bails early. Prevents a slow test from timing out the suite. ```go type Clock interface { Now() time.Time } type fakeClock struct{ t time.Time } func (f *fakeClock) Now() time.Time { return f.t } func TestUserHasCreatedAt(t *testing.T) { clk := &fakeClock{time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)} u, _ := CreateUserWithClock("a@b.co", clk) if u.CreatedAt.Year() != 2024 { t.Fatalf("year=%d, want 2024", u.CreatedAt.Year()) } } ``` ## Mocks and Interfaces (Testing P7 Realism, API P1 Contract Fidelity) - **Mock at the interface, not the struct:** `type Store interface { Get(id string) (*User, error) }` in production; `type mockStore struct{ ... }` in test. The interface is the contract (applies `api/P1`). - **`httptest` for HTTP servers:** `httptest.NewServer` gives a real server on a loopback port; no manual socket plumbing. - **`testify/mock` or hand-written mocks:** hand-written for one-off, `testify` for complex sequencing. Avoid mocking frameworks that generate code at runtime (reflection-heavy) — they hide failures behind stack traces. - **Applies `Testing P7` (realism):** mock the boundary (HTTP, DB), not the unit. Mocking the unit under test tests the mock. ```go type mockStore struct { users map[string]*User got []string } func (m *mockStore) Get(id string) (*User, error) { m.got = append(m.got, id) return m.users[id], nil } func TestGetUserLogs(t *testing.T) { s := &mockStore{users: map[string]*User{"abc": {}}} svc := NewService(s) svc.GetUser("abc") if len(s.got) != 1 || s.got[0] != "abc" { t.Fatalf("got=%v", s.got) } } ``` ## Cross-References - `domains/testing/pyramid.md` — where unit/integration/race tests sit; the race job is its own layer. - `domains/testing/fixtures.md` — `t.TempDir` and `t.Cleanup` as the fixture discipline. - `domains/testing/first-principles.md` — Testing P1 Specification, P2 Independence, P3 Determinism, P9 Edge Coverage. - `domains/concurrency/first-principles.md` — Concurrency P6 (race detector), P10 (test for races). - `languages/go-types.md` — the named types tests assert. - `languages/go-concurrency.md` — concurrent tests exercise the patterns from that doc. - `languages/go-tooling.md` — the `go test` flags (`-race`, `-count`, `-run`) detailed here.