| name | golang-testing |
| description | Production-ready testing practices in Go: table-driven tests, named subtests, test isolation, mock generation/design, integration tests isolation using build tags, and avoiding flakiness. Apply when writing, reviewing, or debugging Go tests. |
| user-invocable | true |
| license | MIT |
| compatibility | Designed for Go testing suites. |
Go Testing Best Practices
This skill outlines guidelines for writing maintainable, fast, and deterministic tests in Go projects.
1. Table-Driven Tests (Idiomatic Go)
Always use table-driven tests for multiple inputs/scenarios. Each scenario MUST have a descriptive name field, which is passed to t.Run.
func TestAdd(t *testing.T) {
tests := []struct {
name string
a, b int
expected int
}{
{
name: "positive numbers",
a: 2,
b: 3,
expected: 5,
},
{
name: "negative and positive",
a: -1,
b: 1,
expected: 0,
},
{
name: "both negative",
a: -2,
b: -3,
expected: -5,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
got := Add(tt.a, tt.b)
if got != tt.expected {
t.Errorf("Add(%d, %d) = %d; want %d", tt.a, tt.b, got, tt.expected)
}
})
}
}
2. Test Parallelism
- Independent unit tests SHOULD run in parallel.
- Call
t.Parallel() inside the main test and also inside individual t.Run subtests.
- Caution: Always capture the range variable (e.g.,
tt := tt) at the top of the loop before running subtests in parallel to prevent goroutines from referring to the last item in the slice.
3. Mocking & Dependencies
- Mock Interfaces, Not Structs:
- Write functions to accept interfaces, enabling easy mocking in tests.
- Minimalist Mocks:
- Prefer hand-written mock structs implementing only the needed methods, or use code generation tools like
mockery or go-mock (avoid manual complex mocks that break on interface changes).
- Use Test Cleanups:
- Use
t.Cleanup(func() { ... }) instead of defer inside subtests for cleaning up test databases, directories, or mocks. t.Cleanup runs even if the test panics or fails early.
4. Integration vs. Unit Tests
- Keep Unit Tests Fast:
- Unit tests should execute in < 10ms and run without network or filesystem dependencies.
- Isolate Integration Tests:
- Separate integration tests (which rely on databases, AWS, or local networks) using build tags:
package mypackage_test
- Run unit tests with standard
go test ./....
- Run integration tests explicitly with
go test -tags=integration ./....
5. Avoiding Test Flakiness
- No Shared State:
- Ensure tests are completely independent and don't share global mutable state.
- Order Independence:
- Goroutine Leak Check:
- Use
go.uber.org/goleak to verify that background goroutines spawned in tests are properly closed before exiting.