Testing
Testing
Section titled “Testing”Commands
Section titled “Commands”| Command | What it does |
|---|---|
make test | Unit tests with -race, -shuffle=on and atomic coverage; writes coverage.out |
make cover | make test, then prints coverage per function |
make cover-html | make test, then opens an HTML report |
make cover-check | make test, then fails if total coverage is below MIN_COV (default 80) |
make lint | golangci-lint run |
make fmt | golangci-lint fmt (gofmt + goimports) |
make vet | go vet ./... |
make check | vet + lint + cover-check; run this before every push |
Run a single package or test with plain Go:
go test ./internal/newsletter/...go test ./internal/newsletter -run TestSubscribeThenConfirm -vChange the threshold for one run: make cover-check MIN_COV=90.
What is excluded from the coverage gate
Section titled “What is excluded from the coverage gate”make test removes two files from the coverage profile before the threshold is
checked (see COVER_EXCLUDE in the Makefile):
cmd/server/is process wiring (flags, signals, listeners).internal/store/firestore.gois a thin adapter over a network client.
Both are deliberately thin. If either grows logic, move the logic into a testable package instead of testing it through the network.
Test style: Detroit school (classicist)
Section titled “Test style: Detroit school (classicist)”- Test behaviour through the public API, with real collaborators. Tests live in
external
_testpackages (for examplenewsletter_test), so they can only use what callers can use. - Replace only I/O at the edges with simple in-memory fakes, not mocks:
store.Memoryfor persistencemail.Outboxrecords sent messages for assertions- a controllable clock (
Now func() time.Time) for expiry tests
- Assert on outcomes (state in the store, messages in the outbox, HTTP status
and
Location), not on which methods were called. - Table-driven tests for validation and rejection cases.
- The one internal test,
mail_internal_test.go, checks message formatting that cannot be reached from the outside without a network.
Writing a new test
Section titled “Writing a new test”- Build a fixture with real objects. See
newFixturein service_test.go. - Drive it through exported functions or HTTP handlers.
- Assert on the store, outbox or response.
- For anything time-dependent, advance the fixture’s clock instead of sleeping.
When a new collaborator performs I/O, add an interface at its boundary plus an
in-memory fake next to the real implementation, as store.Memory and
mail.Outbox do.
Linting
Section titled “Linting”Configuration is in .golangci.yml (golangci-lint v2). It
enables the standard set plus bodyclose, errorlint, gocritic, gosec,
misspell, noctx, nolintlint, revive, unconvert and unparam. gosec and
unparam are relaxed in tests.
Any //nolint needs a specific linter name and a reason.
Tests that build HTTP requests use httptest.NewRequestWithContext, as required
by noctx.
Continuous integration
Section titled “Continuous integration”There is no CI workflow yet. A minimal one runs make check on every pull
request. See Contributing.