How do subtests work with t.Run, and why are they useful?

4 minintermediategotestingsubtestst.Run

Quick Answer

t.Run(name, func(t *testing.T) { ... }) runs a named subtest as its own logical test, with its own pass/fail status reported separately in go test -v output, and its own ability to call t.Fatal/t.Skip without stopping sibling subtests in the same table. This gives you granular failure reporting ("TestAdd/negative_numbers failed", not just "TestAdd failed" with no indication which case broke), the ability to run a single named case in isolation with go test -run TestAdd/negative_numbers, and, when combined with t.Parallel() inside the subtest closure, the ability to run independent subtests concurrently for faster test suites.

Detailed Answer

t.Run turns a flat test function into a tree of independently reportable, independently runnable subtests.

Granular failure reporting

func TestValidate(t *testing.T) {
    cases := map[string]struct {
        input string
        valid bool
    }{
        "empty":     {"", false},
        "valid":     {"ok@example.com", true},
        "no-at":     {"nope", false},
    }
    for name, tc := range cases {
        t.Run(name, func(t *testing.T) {
            got := Validate(tc.input)
            if got != tc.valid {
                t.Errorf("Validate(%q) = %v, want %v", tc.input, got, tc.valid)
            }
        })
    }
}
go test -v -run TestValidate
=== RUN   TestValidate/empty
--- PASS: TestValidate/empty
=== RUN   TestValidate/no-at
--- FAIL: TestValidate/no-at
=== RUN   TestValidate/valid
--- PASS: TestValidate/valid

You immediately know no-at is the failing case, without scanning error messages to figure out which table entry broke.

Running a single subtest

go test -run TestValidate/no-at

The / in -run targets a specific named subtest, which is invaluable when iterating on a fix for one failing case without re-running the entire suite every time.

Parallel subtests

for name, tc := range cases {
    tc := tc  // capture for the closure (needed pre-Go 1.22)
    t.Run(name, func(t *testing.T) {
        t.Parallel()   // this subtest runs concurrently with other parallel subtests
        // ...
    })
}

Calling t.Parallel() inside a subtest tells the test runner it can run concurrently with sibling subtests that also called t.Parallel(), which can meaningfully speed up test suites with many independent, non-interfering cases.

Why this matters practically

Subtests are what make table-driven tests genuinely useful at scale. Without t.Run, a single failing case in a 50-entry table just reports "TestFoo failed," and you're left adding print statements to figure out which one.

Related Resources