table-driven-test

Table-Driven Test Guidelines

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "table-driven-test" with this command: npx skills add cockroachdb/cockroach/cockroachdb-cockroach-table-driven-test

Table-Driven Test Guidelines

Table-driven tests define multiple test cases in a slice of structs, then iterate over them executing the same test logic. This makes it easy to add cases, improves readability, and reduces duplication.

When to use table-driven tests:

  • You have 3+ similar test cases that vary by inputs/outputs

  • Tests follow the same logic pattern with different data

  • Most unit and integration tests benefit from this structure

When to skip:

  • Only 1-2 simple test cases (overhead not worth it)

  • Each test requires completely different logic

  • Test setup/teardown varies significantly between cases

Not the same as: Datadriven tests (different library with testdata files)

Basic Structure

func TestMyFunction(t *testing.T) { tests := []struct { name string input string expectedLen int }{ {name: "basic case", input: "hello", expectedLen: 5}, {name: "empty input", input: "", expectedLen: 0}, }

for _, tc := range tests {
    t.Run(tc.name, func(t *testing.T) {
        result, err := MyFunction(tc.input)
        require.NoError(t, err)
        require.Equal(t, tc.expectedLen, result)
    })
}

}

Core Principles

  1. Only Specify What's Necessary

Bad:

{ name: "remap table", tableID: 100, tableName: "users", schemaID: 1, schemaName: "public", databaseID: 50, databaseName: "mydb", expectedID: 51, // only this is actually tested! }

Good:

{name: "remap table", tableID: 100, expectedID: 51}

  1. Struct Field Ordering: Inputs First, Then Expected

Order struct fields with input fields at the top and verification fields at the bottom. Prefix all verification fields with expected so readers can immediately distinguish inputs from outputs.

Bad:

tests := []struct { name string wantErr bool // verification mixed with inputs input string output int // unclear if this is input or verification }

Good:

tests := []struct { name string input string expectedCount int expectedErr string }

  1. One Concern Per Test Case

Bad:

{ name: "multiple behaviors", input: map[int]int{ 10: 60, // normal remapping 49: 99, // edge case 50: 50, // system table preservation }, }

Good:

{name: "normal remapping", input: map[int]int{10: 60}}, {name: "preserve system table IDs under 50", input: map[int]int{49: 49}}, {name: "remap IDs at or above 50", input: map[int]int{50: 100}},

  1. Independent Test Cases

Each case should be self-contained. Don't build dependent state across cases.

  1. Names Describe Intent, Not Inputs

Test case names should hint at the intention or scenario, not duplicate the input data. A reader should understand what the case is testing from the name alone.

  • Good: "two matched regions" , "error on negative input"

  • Bad: "match region x and y" , "test1" , "input_abc"

The name should answer "what scenario is this?" not "what data does this use?"

Assertions

Use require.* (stops on failure) for most checks. Use assert.* (continues on failure) only when you want to see multiple failures.

Common patterns:

// Errors require.NoError(t, err) require.Error(t, err) require.ErrorContains(t, err, "not found")

// Equality require.Equal(t, expected, actual) // shows both values on failure require.True(t, result == expected) // don't do this - hides values

// Collections require.Len(t, slice, expectedLen) require.Contains(t, slice, element)

Avoid redundant nil checks: Don't use require.NotNil before an assertion that will already fail on nil (like require.Equal or require.Contains ). The subsequent assertion provides a clearer failure message anyway.

// Bad: redundant nil check require.NotNil(t, result) require.Equal(t, expectedVal, result.Field)

// Good: Equal already fails clearly if result is nil require.Equal(t, expectedVal, result.Field)

Error handling in test cases:

tests := []struct { name string input string expectedErr string }{ {name: "valid input", input: "hello"}, {name: "empty input rejected", input: "", expectedErr: "must not be empty"}, }

for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { err := Validate(tc.input) if tc.expectedErr != "" { require.ErrorContains(t, err, tc.expectedErr) } else { require.NoError(t, err) } }) }

Variadic Helper Functions

Use helpers to reduce boilerplate and make test data readable.

Example from CockroachDB (pkg/backup/compaction_dist_test.go ):

// Helper types type mockEntry struct { span roachpb.Span locality string }

// Variadic helpers func entry(start, end string, locality string) mockEntry { return mockEntry{ span: mockSpan(start, end), locality: locality, } }

func entries(specs ...mockEntry) []execinfrapb.RestoreSpanEntry { var entries []execinfrapb.RestoreSpanEntry for _, s := range specs { var dir cloudpb.ExternalStorage if s.locality != "" { dir = cloudpb.ExternalStorage{ URI: "nodelocal://1/test?COCKROACH_LOCALITY=" + s.locality, } } entries = append(entries, execinfrapb.RestoreSpanEntry{ Span: s.span, Files: []execinfrapb.RestoreFileSpec{{Dir: dir}}, }) } return entries }

// Usage - reads like a specification entries := entries( entry("a", "b", "dc=dc1"), entry("c", "d", "dc=dc2"), entry("e", "f", "dc=dc3"), )

When to create helpers:

  • Complex struct initialization obscures test intent

  • Patterns repeat across test cases

  • Building composite data structures

When NOT to use:

  • Simple values that don't need transformation

  • One-off test cases

  • Helpers add more complexity than they remove

Integration Tests

For tests requiring a database server, see the /integration-test skill. The table-driven patterns here apply to both unit and integration tests.

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

commit-helper

No summary provided by upstream source.

Repository SourceNeeds Review
General

backport-pr-assistant

No summary provided by upstream source.

Repository SourceNeeds Review
General

reduce-unoptimized-query-oracle

No summary provided by upstream source.

Repository SourceNeeds Review