Go cookbook · Beginner

Write table-driven tests with subtests

Tests for a function with many cases are long, repetitive and stop at the first failure.

Create a free account to save this page, take notes and track your progress. Sign in

Approach

List cases in a slice of structs, run each with t.Run (and t.Parallel() when independent), and report differences clearly. Include edge cases and error cases.

Solution

package pricing

import (
	"errors"
	"testing"
)

var ErrInvalidQuantity = errors.New("invalid quantity")

func Total(unitCents, quantity int, coupon string) (int, error) {
	if quantity < 1 {
		return 0, ErrInvalidQuantity
	}
	total := unitCents * quantity
	if coupon == "TEN" {
		total -= total / 10
	}
	return total, nil
}

func TestTotal(t *testing.T) {
	tests := []struct {
		name     string
		unit     int
		quantity int
		coupon   string
		want     int
		wantErr  error
	}{
		{name: "single item", unit: 2400, quantity: 1, want: 2400},
		{name: "several items", unit: 850, quantity: 3, want: 2550},
		{name: "coupon takes ten percent", unit: 1000, quantity: 2, coupon: "TEN", want: 1800},
		{name: "unknown coupon ignored", unit: 1000, quantity: 1, coupon: "NOPE", want: 1000},
		{name: "zero quantity", unit: 1000, quantity: 0, wantErr: ErrInvalidQuantity},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()
			got, err := Total(tt.unit, tt.quantity, tt.coupon)
			if !errors.Is(err, tt.wantErr) {
				t.Fatalf("error = %v, want %v", err, tt.wantErr)
			}
			if got != tt.want {
				t.Errorf("Total(%d, %d, %q) = %d, want %d", tt.unit, tt.quantity, tt.coupon, got, tt.want)
			}
		})
	}
}

Pitfalls

  • Test names that don't describe the case, making failures hard to read.
  • Sharing mutable state between parallel subtests.
  • Only testing happy paths.

Variations

  • Run one case with go test -run 'TestTotal/coupon'.
  • github.com/google/go-cmp/cmp.Diff produces readable diffs for structs.

Further reading: Subtests

Reference: https://pkg.go.dev/testing#hdr-Subtests_and_Sub_benchmarks

More Go recipes

Was this page helpful?