Go Table Tests
A table test is a test that checks many cases from one list of rows. Each row holds one set of inputs and the result those inputs should give.
One loop checks every row
This test file sits beside the code it tests and runs with the go test command. The table lists three cases for Clamp with bounds 0 and 10, and one loop checks each row inside a single test.
package main
import "testing"
func TestClamp(t *testing.T) {
tests := []struct {
v int
lo int
hi int
want int
}{
{v: -5, lo: 0, hi: 10, want: 0},
{v: 5, lo: 0, hi: 10, want: 5},
{v: 15, lo: 0, hi: 10, want: 10},
}
for _, tc := range tests {
if got := Clamp(tc.v, tc.lo, tc.hi); got != tc.want {
t.Errorf("Clamp(%d, %d, %d) = %d, want %d", tc.v, tc.lo, tc.hi, got, tc.want)
}
}
}Each row becomes a subtest
The same table now gives each row a name. t.Run runs each row as a subtest, and each subtest prints its own PASS line.
package main
import "testing"
func TestClamp(t *testing.T) {
tests := []struct {
name string
v int
lo int
hi int
want int
}{
{name: "below", v: -5, lo: 0, hi: 10, want: 0},
{name: "inside", v: 5, lo: 0, hi: 10, want: 5},
{name: "above", v: 15, lo: 0, hi: 10, want: 10},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
if got := Clamp(tc.v, tc.lo, tc.hi); got != tc.want {
t.Errorf("Clamp(%d, %d, %d) = %d, want %d", tc.v, tc.lo, tc.hi, got, tc.want)
}
})
}
}A table of words as answers
Grade turns a score into a letter string. Each named row holds one score and the letter it should earn.
package main
import "testing"
func TestGrade(t *testing.T) {
tests := []struct {
name string
score int
want string
}{
{name: "top", score: 92, want: "A"},
{name: "middle", score: 74, want: "B"},
{name: "bottom", score: 30, want: "F"},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
if got := Grade(tc.score); got != tc.want {
t.Errorf("Grade(%d) = %s, want %s", tc.score, got, tc.want)
}
})
}
}Example
package main
import "testing"
func TestClamp(t *testing.T) {
tests := []struct {
name string
v int
lo int
hi int
want int
}{
{name: "below", v: -8, lo: 0, hi: 10, want: 0},
{name: "edge", v: 0, lo: 0, hi: 10, want: 0},
{name: "inside", v: 7, lo: 0, hi: 10, want: 7},
{name: "above", v: 12, lo: 0, hi: 10, want: 10},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
if got := Clamp(tc.v, tc.lo, tc.hi); got != tc.want {
t.Errorf("Clamp(%d, %d, %d) = %d, want %d", tc.v, tc.lo, tc.hi, got, tc.want)
}
})
}
}Key points
- A table test keeps its cases in one slice of rows.
- Each row holds the inputs for one case and the wanted result.
- One loop runs the same check for every row.
- Use t.Run to run one row as a named subtest.
- A subtest prints its own PASS or FAIL line under the main test.
- One check written once can cover every row in the table.
In this example, the table holds four rows for Clamp with bounds 0 and 10. The rows cover a value below the range, a value on the edge, a value inside, and a value above. t.Run turns each row into a named subtest. All four subtests pass under the main test.
Try it Yourself
Edit the code, then press Run.
Running is simulated ยท Go Playground
Exercise
What does t.Run add to a table test?