liveBook · Manning

Table-driven test

As explained in Go by Example

Table-driven testing is a testing technique that represents multiple test cases as data and executes a common test procedure for each case.

A table-driven test represents many test scenarios as data, then runs one shared procedure against every row. In Go, this idiom separates inputs and expected results from assertion logic, reducing repeated test code while making new cases easy to add. section 2.4

A test table is commonly a slice of anonymous structs. Give each row a descriptive name, the input to pass to the function, and the expected result. Keeping the table in test code allows multiple tests to reuse the scenarios without placing test-only data in production builds. section 2.4.1

Listing 2.7 A table-driven test case (url/url_test.go)
var parseTests = []struct { #1
    name string  #1
    uri  string  #1
    want *URL  #1
}{
    {  #2
        name: "full",   #2
        uri:  "https://github.com/inancgumus",   #2
        want: &URL{   #2
            Scheme: "https",   #2
            Host:   "github.com",   #2
            Path:   "inancgumus",   #2
        },   #2
    },   #2
    {  #3
        name: "without_path",   #3
        uri:  "https://github.com",   #3
        want: &URL{   #3
            Scheme: "https",   #3
            Host:   "github.com",   #3
            Path:   "",   #3
        },   #3
    },   #3
    /* many more test cases can be easily added */ #3
} #3

This test-case table names URL parsing scenarios and pairs each URI with the URL value expected from Parse. section 2.4.1

Build a table-driven test in a direct loop:

  • Define a row type with fields for a case name, input, and expected output. section 2.4.1
  • Populate a slice with representative scenarios, including cases discovered when fixing bugs. section 2.4.1
  • Range over the slice, call the function with each row’s input, and compare got with want. section 2.4.2
  • Log the case name so a failing run identifies the scenario being exercised. section 2.4.2

Testing URL parsing

A Parse test can store full and pathless URLs in parseTests. For each row, it calls Parse(tt.uri), treats an unexpected error as fatal, and compares the returned URL with tt.want. The same test logic therefore checks every listed URL shape. section 2.4.1 section 2.4.2

Listing 2.8 A table-driven test (url/url_test.go)
func TestParseTable(t *testing.T) {
    for _, tt := range parseTests {  #1
        t.Logf("run %s", tt.name)  #2

        got, err := Parse(tt.uri)  #3
        if err != nil {   #3
            t.Fatalf(   #3
                "Parse(%q) err = %v, want <nil>",   #3
                tt.uri, err,    #3
            )   #3
        }    #3
        if *got != *tt.want {  #3
            t.Errorf(  #3
                "Parse(%q)\ngot  %#v\nwant %#v",  #3
                tt.uri, got, tt.want,  #3
            ) 
        }
    }
}

The loop shows the core arrangement: retrieve each row, run Parse with its input, and report differences between got and want. section 2.4.2

Consolidation has a trade-off: all rows run inside one test function. A call to Fatalf for an early row ends that function and skips later cases, and a run filter cannot select an individual row. Use subtests when each case needs independent failure isolation or selective execution. section 2.4.4 section 2.5.1

Run a focused table test with go test and the -run flag, using an anchored pattern when needed. This targets the test function, not a particular table row; subtests extend the approach by giving rows separately runnable names. section 2.4.3 section 2.5.1

How Go by Example covers it

How deep it goes
Works through table declaration, iteration, logging, assertions, execution filtering, and fatal-failure limitations before deferring isolation to subtests.
Examples
Demonstrates Parse against full, pathless, and data-scheme URL cases stored in an anonymous struct slice.
What it assumes
Assumes basic Go testing and benefits from familiarity with slices and anonymous structs.
What you can do after
Apply it in Go tests to add scenarios as data while reusing one test procedure.
How it connects
Connects Go testing with subtests, the go test run flag, Fatalf, Errorf, and got/want comparisons.

Also explained in

Compare how 6 books explain table-driven test →

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.