Use t.Run(name, func(t *testing.T)) to define subtests within a parent test function, allowing you to run multiple test cases in parallel while keeping the test output organized by name. This approach improves test isolation and enables parallel execution of independent cases using t.Parallel() inside the subtest.
Here is a practical example demonstrating how to structure subtests with parallel execution and proper cleanup:
func TestCalculate(t *testing.T) {
tests := []struct {
name string
input int
expected int
}{
{"double 5", 5, 10},
{"double 0", 0, 0},
{"double -3", -3, -6},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel() // Enable parallel execution for this subtest
result := tt.input * 2
if result != tt.expected {
t.Errorf("got %d, want %d", result, tt.expected)
}
})
}
}
When you run this test with go test -v, the output will clearly separate each subtest result. If you add the -race flag, the Go race detector will correctly identify data races between parallel subtests, which is a common pitfall if you share mutable state without synchronization.
To run only a specific subtest, use the -run flag with the full test name path:
go test -run TestCalculate/double_5 -v
This is useful for debugging a specific failing case without running the entire suite.
Key things to remember:
- Isolation: Each subtest gets its own
*testing.Tinstance. Do not share mutable global variables between subtests unless you synchronize access, as they may run in parallel. - Failures: If a subtest fails, the parent test continues running other subtests unless you call
t.FailNow()ort.Fatal()inside the subtest. - Cleanup: Use
t.Cleanup()inside the subtest function to ensure resources are released even if the subtest fails or is skipped.
t.Run("with cleanup", func(t *testing.T) {
t.Parallel()
file, err := os.CreateTemp("", "test")
if err != nil {
t.Fatal(err)
}
// Ensure file is removed even if test fails
t.Cleanup(func() {
os.Remove(file.Name())
})
// Test logic here
})
This pattern is the standard way to organize complex test scenarios in Go, making your test suite faster and easier to maintain.