liveBook · Manning

Subtest

As explained in Go by Example

A subtest is an independently named test executed as part of a parent test and reported separately.

A subtest is a named test function run under a parent test. In Go, call t.Run with a name and a function containing one case’s assertions. Each subtest receives its own *testing.T, so its Errorf or Fatalf reports failure for that subtest rather than directly stopping its siblings. section 2.5.1 section 2.5.3

Subtests are especially useful for table-driven tests. A plain table loop puts every case in one test function: a Fatalf in an early case stops the function and skips later cases. Wrapping each row in t.Run keeps the shared test logic while isolating cases, producing a fuller test report after failures. section 2.4.4 section 2.5.2

Listing 2.10 Table-driven subtests (url/url_test.go)
var parseTests = []struct {
    . . .
}{
    {
        name: "with_data_scheme",
        . . .
    },
    {
        name: "full",
        . . .
    },
    {
        name: "without_path",
        . . .
    },
}

func TestParseSubtests(t *testing.T) {
    // We can put common test setup and teardown logic here.

    for _, tt := range parseTests {
        t.Run(tt.name, func(t *testing.T) {  #1
            got, err := Parse(tt.uri)
            if err != nil {
                t.Fatalf(
                    "Parse(%q) err = %v, want <nil>",
                    tt.uri, err,
                )
            }
            if *got != *tt.want {
                t.Errorf(
                    "Parse(%q)\ngot  %#v\nwant %#v",
                    tt.uri, got, tt.want,
                )
            }
        })
    }
}

This pattern iterates over named URL cases and invokes t.Run once per row. The closure performs the same Parse checks as a conventional table-driven test, while the parent remains the place for setup and teardown shared by all cases. section 2.5.2

Use subtests to make a test suite easier to inspect and control:

  • Give each case a unique, descriptive name. Names appear beneath the parent in verbose test output, making each scenario’s purpose and status visible. section 2.5.2 section 2.5.4
  • Select one nested case with go test -run using its parent/subtest name, such as TestParseSubtests/full. The parent invokes Run for all cases, but the test filter skips nonmatching subtests. section 2.5.4
  • Put common setup and teardown in the parent when sibling cases can share it, for example opening and closing a database connection once around the subtests. section 2.5.2
  • Keep each *testing.T with its own test function. Sharing a T between tests or subtests can make results unpredictable and compromise their validity. section 2.5.3

Isolating URL parser cases

A parser test table includes with_data_scheme, full, and without_path cases. If the data-scheme case calls Fatalf in one ordinary table-driven test, full and without_path never run. As subtests, the data-scheme case can fail while the other two still run and report their own results. After Parse is fixed, the same named cases all pass. section 2.4.4 section 2.5.4 section 2.5.5

A parent normally runs its subtests sequentially and waits for each to finish. Subtests can also run in parallel, but both the parent and each participating subtest must be marked parallel; otherwise parallel subtests wait until the parent completes. Use this when independent cases benefit from concurrent execution. section 2.5.4 section 3.4.2

Subtests are hierarchical: a subtest can run another subtest. Top-level tests are also run internally through Run, so subtests extend the testing package’s existing execution model rather than forming a separate kind of test. section 2.5.5

How Go by Example covers it

How deep it goes
Explains Run closures, per-subtest *testing.T values, reporting, filtering, hierarchy, and parallel execution requirements.
Examples
Demonstrates named URL parsing cases for data schemes, full URLs, and URLs without paths, plus parallel query subtests.
What it assumes
Assumes Go testing fundamentals, table-driven tests, closures, goroutines, and *testing.T; points new Go readers to foundational appendices.
What you can do after
Apply it via Go’s testing package to run, filter, report, and optionally parallelize independently failing test cases.
How it connects
Connects table-driven testing, closures, *testing.T, Fatalf, Errorf, goroutines, setup and teardown, and parallel tests.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Go by Example. The section links point to the parts of the book it draws on.