๐ŸŽ“ A1Learner
๐Ÿ”
0 of 28 lessons completed

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?