All skills
cxuu avatar

/go-testing

@91f0c2e
by Charles Xucxuu/golang-skills165 stars
19

Use when writing, reviewing, or improving Go test code — including table-driven tests, subtests, parallel tests, test helpers, test doubles, and assertions with cmp.Diff. Also use when a user asks to write a test for a Go function, even if they don't mention specific patterns like table-driven tests or subtests. Does not cover benchmark performance testing (see go-performance).

Use this Skill: https://skilld.dev/gh/cxuu/golang-skills/go-testing

This session only. Nothing lands on disk.

referencesTABLE-DRIVEN-TESTS.md

≈972 tokens on demand. Your agent reads this file only when SKILL.md points to it.

Table-Driven Tests, Subtests, and Parallel Tests

Detailed reference for structuring table-driven tests and subtests in Go. Sources: Google Go Style Guide, Uber Go Style Guide.


Basic Structure

func TestCompare(t *testing.T) {
    tests := []struct {
        a, b string
        want int
    }{
        {"", "", 0},
        {"a", "", 1},
        {"", "a", -1},
        {"abc", "abc", 0},
    }
    for _, tt := range tests {
        got := Compare(tt.a, tt.b)
        if got != tt.want {
            t.Errorf("Compare(%q, %q) = %v, want %v", tt.a, tt.b, got, tt.want)
        }
    }
}

Best Practices

Use field names when test cases span many lines or have adjacent fields of the same type:

tests := []struct {
    name  string
    input string
    want  int
}{
    {name: "empty", input: "", want: 0},
    {name: "single", input: "a", want: 1},
}

Don't identify rows by index — include inputs in failure messages instead of Case #%d failed.


Avoid Complexity in Table Tests

When test cases need complex setup, conditional mocking, or multiple branches, prefer separate test functions over table tests.

// Bad: Too many conditional fields make tests hard to understand
tests := []struct {
    give          string
    want          string
    wantErr       error
    shouldCallX   bool
    shouldCallY   bool
    giveXResponse string
    giveXErr      error
    giveYResponse string
    giveYErr      error
}{...}

for _, tt := range tests {
    t.Run(tt.give, func(t *testing.T) {
        if tt.shouldCallX {
            xMock.EXPECT().Call().Return(tt.giveXResponse, tt.giveXErr)
        }
        if tt.shouldCallY {
            yMock.EXPECT().Call().Return(tt.giveYResponse, tt.giveYErr)
        }
        // ...
    })
}

// Good: Separate focused tests are clearer
func TestShouldCallX(t *testing.T) {
    xMock.EXPECT().Call().Return("XResponse", nil)
    got, err := DoComplexThing("inputX", xMock, yMock)
    // assert...
}

func TestShouldCallYAndFail(t *testing.T) {
    yMock.EXPECT().Call().Return("YResponse", nil)
    _, err := DoComplexThing("inputY", xMock, yMock)
    // assert error...
}

Table tests work best when:

  • All cases run identical logic (no conditional assertions)
  • Setup is the same for all cases
  • No conditional mocking based on test case fields
  • All table fields are used in all tests

A single shouldErr field for success/failure is acceptable if the test body is short and straightforward.


Subtests

Use t.Run for better organization, filtering, and parallel execution.

Subtest Names

  • Use clear, concise names: t.Run("empty_input", ...), t.Run("hu_to_en", ...)
  • Avoid wordy descriptions or slashes (slashes break test filtering)
  • Subtests must be independent — no shared state or execution order dependencies

Table Tests with Subtests

func TestTranslate(t *testing.T) {
    tests := []struct {
        name, srcLang, dstLang, input, want string
    }{
        {"hu_en_basic", "hu", "en", "köszönöm", "thank you"},
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            if got := Translate(tt.srcLang, tt.dstLang, tt.input); got != tt.want {
                t.Errorf("Translate(%q, %q, %q) = %q, want %q",
                    tt.srcLang, tt.dstLang, tt.input, got, tt.want)
            }
        })
    }
}

Parallel Tests

When using t.Parallel() in table tests, be aware of loop variable capture:

for _, tt := range tests {
    t.Run(tt.name, func(t *testing.T) {
        t.Parallel()
        // Go 1.22+: tt is correctly captured per iteration
        // Go 1.21-: add "tt := tt" here to capture the variable
        got := Process(tt.give)
        if got != tt.want {
            t.Errorf("Process(%q) = %q, want %q", tt.give, got, tt.want)
        }
    })
}

Source: SKILL.md on GitHub

No alerts17d5 checks · Risk SAFE
  • Gen Agent Trust Hub17d

    The skill is safe and follows Go testing best practices. It includes a well-sanitized script for generating test scaffolds, though it presents a minor surface for indirect prompt injection common to code-generation tools.

  • Socket17d

    No alerts

  • Snyk17d

    Risk: LOW · No issues

  • Runlayer6mo

    8 files scanned · No issues

  • ZeroLeaks5mo

    1 finding · Score: 86/100

Signed by skilld at 91f0c2e. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 months ago.

Steadyupdated 3 months ago
What it can do
Runs commands
All 1 allowed tools
Bash(bash:*)

README badge

README badge for cxuu/golang-skills/go-testing