From xe-go
Write Go table-driven tests following established patterns. Use when writing tests, creating test functions, adding test cases, or when the user mentions "test", "table-driven", "Go tests", or testing in Go codebases.
How this skill is triggered — by the user, by Claude, or both
Slash command
/xe-go:go-table-driven-testsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Use this skill when writing or modifying Go table-driven tests. It ensures tests follow established patterns.
Use this skill when writing or modifying Go table-driven tests. It ensures tests follow established patterns.
t.Run()name field that becomes the subtest namet.Helper() in test helpers for proper line reportingfunc TestFunctionName(t *testing.T) {
tests := []struct {
name string // required: subtest name
input Type // function input
want Type // expected output
wantErr error // expected error (nil for success)
errCheck func(error) bool // optional: custom error validation
setupEnv func() func() // optional: env setup, returns cleanup
}{
{
name: "descriptive case name",
input: "test input",
want: "expected output",
},
// ... more cases
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// test implementation using tt fields
})
}
}
| Field | Required | Purpose |
|---|---|---|
name | Yes | Subtest name - be descriptive and specific |
input/args | Varies | Input values for the function under test |
want/want* | Varies | Expected output values (e.g., wantErr, wantResult) |
errCheck | No | Custom error validation function |
setupEnv | No | Environment setup function returning cleanup |
Test<FunctionName> or Test<FunctionName>_<Scenario>input/argswant (e.g., want, wantErr, wantResult)func TestWithRegion(t *testing.T) {
tests := []struct {
name string
region string
}{
{"auto region", "auto"},
{"us-west-2", "us-west-2"},
{"eu-central-1", "eu-central-1"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
o := &Options{}
WithRegion(tt.region)(o)
if o.Region != tt.region {
t.Errorf("Region = %v, want %v", o.Region, tt.region)
}
})
}
}
wantErrfunc TestNew_errorCases(t *testing.T) {
tests := []struct {
name string
input string
wantErr error
}{
{"empty input", "", ErrInvalidInput},
{"invalid input", "!!!", ErrInvalidInput},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := Parse(tt.input)
if !errors.Is(err, tt.wantErr) {
t.Errorf("error = %v, want %v", err, tt.wantErr)
}
})
}
}
errCheckfunc TestNew_customErrors(t *testing.T) {
tests := []struct {
name string
setupEnv func() func()
wantErr error
errCheck func(error) bool
}{
{
name: "no bucket name returns ErrNoBucketName",
setupEnv: func() func() { return func() {} },
wantErr: ErrNoBucketName,
errCheck: func(err error) bool {
return errors.Is(err, ErrNoBucketName)
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cleanup := tt.setupEnv()
defer cleanup()
_, err := New(context.Background())
if tt.wantErr != nil {
if tt.errCheck != nil {
if !tt.errCheck(err) {
t.Errorf("error = %v, want %v", err, tt.wantErr)
}
}
}
})
}
}
setupEnvfunc TestNew_envVarOverrides(t *testing.T) {
tests := []struct {
name string
setupEnv func() func()
options []Option
wantErr error
}{
{
name: "bucket from env var",
setupEnv: func() func() {
os.Setenv("TIGRIS_STORAGE_BUCKET", "test-bucket")
return func() { os.Unsetenv("TIGRIS_STORAGE_BUCKET") }
},
wantErr: nil,
},
{
name: "bucket from option overrides env var",
setupEnv: func() func() {
os.Setenv("TIGRIS_STORAGE_BUCKET", "env-bucket")
return func() { os.Unsetenv("TIGRIS_STORAGE_BUCKET") }
},
options: []Option{
func(o *Options) { o.BucketName = "option-bucket" },
},
wantErr: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cleanup := tt.setupEnv()
defer cleanup()
_, err := New(context.Background(), tt.options...)
if tt.wantErr != nil && !errors.Is(err, tt.wantErr) {
t.Errorf("error = %v, want %v", err, tt.wantErr)
}
})
}
}
For tests requiring real credentials, use a skip helper:
// skipIfNoCreds skips the test if Tigris credentials are not set.
// Use this for integration tests that require real Tigris operations.
func skipIfNoCreds(t *testing.T) {
t.Helper()
if os.Getenv("TIGRIS_STORAGE_ACCESS_KEY_ID") == "" ||
os.Getenv("TIGRIS_STORAGE_SECRET_ACCESS_KEY") == "" {
t.Skip("skipping: TIGRIS_STORAGE_ACCESS_KEY_ID and TIGRIS_STORAGE_SECRET_ACCESS_KEY not set")
}
}
func TestCreateBucket(t *testing.T) {
tests := []struct {
name string
bucket string
options []BucketOption
wantErr error
}{
{
name: "create snapshot-enabled bucket",
bucket: "test-bucket",
options: []BucketOption{WithEnableSnapshot()},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
skipIfNoCreds(t)
// test implementation
})
}
}
Use t.Helper() in helper functions for proper line number reporting:
func setupTestBucket(t *testing.T, ctx context.Context, client *Client) string {
t.Helper()
skipIfNoCreds(t)
bucket := "test-bucket-" + randomSuffix()
err := client.CreateBucket(ctx, bucket)
if err != nil {
t.Fatalf("failed to create test bucket: %v", err)
}
return bucket
}
func cleanupTestBucket(t *testing.T, ctx context.Context, client *Client, bucket string) {
t.Helper()
err := client.DeleteBucket(ctx, bucket, WithForceDelete())
if err != nil {
t.Logf("warning: failed to cleanup test bucket %s: %v", bucket, err)
}
}
When writing table-driven tests:
name field as first fieldinput)wantt.Run(tt.name, func(t *testing.T) { ... })errors.Is() for error comparisondeferskipIfNoCreds(t) helpert.Helper() for proper line reporting*_test.go and lives next to the code it testsInclude both actual and expected values in error messages for clear failure diagnosis:
t.Errorf("got %q, want %q", actual, expected)
Note: t.Errorf is not an assertion - the test continues after logging. This helps identify whether failures are systematic or isolated to specific cases.
Consider using a map instead of a slice for test cases. Map iteration order is non-deterministic, which ensures test cases are truly independent:
tests := map[string]struct {
input string
want string
}{
"empty string": {input: "", want: ""},
"single character": {input: "x", want: "x"},
"multi-byte glyph": {input: "🎉", want: "🎉"},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
got := process(tt.input)
if got != tt.want {
t.Errorf("got %q, want %q", got, tt.want)
}
})
}
Add t.Parallel() calls to run test cases in parallel. The loop variable is automatically captured per iteration:
func TestFunction(t *testing.T) {
tests := []struct {
name string
input string
}{
// ... test cases
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel() // marks this subtest as parallel
// test implementation
})
}
}
Provides a checklist for code reviews covering functionality, security, performance, maintainability, tests, and quality. Use for pull requests, audits, team standards, and developer training.
npx claudepluginhub xe/agent-plugins --plugin xe-go