Go

Lesson 09

Table-driven tests

Use table-driven tests to make related cases explicit while keeping each case named and independently diagnosable.

Good Code

reviews/slug_test.go
func TestSlugify(t *testing.T) {
    // Each row names one variation of the same behavior.
    tests := []struct {
        name string
        in   string
        want string
    }{
        {name: "lowercases title", in: "Code Review", want: "code-review"},
        {name: "trims spaces", in: "  Draft Notes  ", want: "draft-notes"},
    }

    for _, tt := range tests {
        // Subtests make failures point to the exact case.
        t.Run(tt.name, func(t *testing.T) {
            got := Slugify(tt.in)
            if got != tt.want {
                t.Fatalf("Slugify(%q) = %q, want %q", tt.in, got, tt.want)
            }
        })
    }
}

Bad Code

reviews/slug_test.go
func TestSlugify(t *testing.T) {
    // Repeated assertions give vague failures.
    if Slugify("Code Review") != "code-review" {
        t.Fatal("bad slug")
    }

    if Slugify("  Draft Notes  ") != "draft-notes" {
        t.Fatal("bad slug")
    }
}

Review Notes

What to review

Good Code

The good version names each case, uses subtests, and includes enough failure output to understand the broken input and expected value.

Bad Code

The bad version repeats assertions with vague messages. When it fails, the reviewer has to reverse engineer the case.

Takeaways

  • A test table should improve coverage and readability, not hide which case failed.