Agent skill
go-testing
Use when writing, reviewing, or debugging Go tests and benchmarks. Covers table-driven tests, parallel execution, go-cmp, T.Context, T.Chdir, b.Loop, synctest for deterministic concurrency testing, and test failure messages.
Install this agent skill to your Project
npx add-skill https://github.com/saisudhir14/claude-skills/tree/main/skills/go-testing
Metadata
Additional technical details for this skill
- tags
- golang go testing benchmark table-driven synctest go-cmp
- author
- saisudhir14
- category
- languages
SKILL.md
Go Testing
Production-grade testing patterns for Go.
Table-Driven Tests with Parallel Execution
func TestSplit(t *testing.T) {
tests := []struct {
name string
input string
sep string
want []string
}{
{name: "simple", input: "a/b/c", sep: "/", want: []string{"a", "b", "c"}},
{name: "empty", input: "", sep: "/", want: []string{""}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := strings.Split(tt.input, tt.sep)
if diff := cmp.Diff(tt.want, got); diff != "" {
t.Errorf("Split() mismatch (-want +got):\n%s", diff)
}
})
}
}
T.Context and T.Chdir (Go 1.24+)
func TestWithContext(t *testing.T) {
// T.Context returns a context canceled after test completes
// but before cleanup functions run
ctx := t.Context()
result, err := doWork(ctx)
if err != nil {
t.Fatal(err)
}
}
func TestWithChdir(t *testing.T) {
// T.Chdir changes working directory for duration of test
// and automatically restores it after
t.Chdir("testdata")
data, err := os.ReadFile("input.txt")
// ...
}
Benchmark with b.Loop (Go 1.24+)
// Old way - error prone
func BenchmarkOld(b *testing.B) {
input := setupInput() // counted in benchmark time!
b.ResetTimer()
for i := 0; i < b.N; i++ {
process(input) // compiler might optimize away
}
}
// Go 1.24+ - preferred
func BenchmarkNew(b *testing.B) {
input := setupInput() // setup runs once, excluded from timing
for b.Loop() {
process(input) // compiler cannot optimize away
}
}
Benefits of b.Loop():
- Setup code runs exactly once per
-count, automatically excluded from timing - No need to call
b.ResetTimer() - Function call parameters and results are kept alive, preventing compiler optimization
Testing Concurrent Code with synctest (Go 1.25+)
The testing/synctest package provides deterministic testing for concurrent code using synthetic time.
import "testing/synctest"
func TestTimeout(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
defer cancel()
time.Sleep(4 * time.Second) // instant in real time
if err := ctx.Err(); err != nil {
t.Fatalf("unexpected timeout: %v", err)
}
time.Sleep(2 * time.Second)
if ctx.Err() != context.DeadlineExceeded {
t.Fatal("expected deadline exceeded")
}
})
}
Key concepts:
synctest.Testcreates an isolated "bubble" with synthetic time- Time only advances when all goroutines in the bubble are blocked
synctest.Wait()waits for all goroutines to be durably blocked
func TestPeriodicTask(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
var count atomic.Int64
ctx, cancel := context.WithCancel(t.Context())
go func() {
ticker := time.NewTicker(100 * time.Millisecond)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
count.Add(1)
}
}
}()
time.Sleep(350 * time.Millisecond)
synctest.Wait()
cancel()
synctest.Wait()
if got := count.Load(); got != 3 {
t.Errorf("got %d ticks, want 3", got)
}
})
}
Restrictions in synctest bubbles: Do not call t.Run(), t.Parallel(), or t.Deadline().
Use go-cmp for Comparisons
Prefer github.com/google/go-cmp/cmp over reflect.DeepEqual.
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("mismatch (-want +got):\n%s", diff)
}
// With options
if diff := cmp.Diff(want, got, cmpopts.IgnoreUnexported(User{})); diff != "" {
t.Errorf("mismatch (-want +got):\n%s", diff)
}
Useful Test Failures
Include: what was wrong, inputs, actual result, expected result.
// Wrong
if got != want { t.Error("wrong result") }
// Correct
if got != want { t.Errorf("Foo(%q) = %d; want %d", input, got, want) }
Use t.Fatal for Setup Failures
f, err := os.CreateTemp("", "test")
if err != nil {
t.Fatal("failed to set up test")
}
Interfaces Belong to Consumers
Define interfaces in the package that uses them, not the package that implements them.
// Producer returns concrete type
package producer
type Thinger struct{}
func NewThinger() *Thinger { return &Thinger{} }
// Consumer defines interface it needs
package consumer
type Thinger interface { Thing() bool }
func Process(t Thinger) { }
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
go-project-layout
Use when starting a new Go project, organizing packages, or restructuring an existing Go codebase. Covers standard directory layout, package design, Makefile targets, Dockerfile patterns, and module setup.
go-code-review
Use when reviewing Go code or preparing code for review. Quick-reference checklist covering naming, error handling, concurrency, testing, imports, documentation, and common pitfalls. Based on Go Wiki CodeReviewComments.
go-concurrency
Use when writing, reviewing, or debugging concurrent Go code. Covers goroutine lifecycle management, channels, errgroup, mutexes, atomics, sync.Map, and synchronous-first design. Based on Google and Uber style guides.
go-security
Use when writing, reviewing, or auditing Go code for security. Covers input validation, SQL injection prevention, path traversal, secrets management, cryptography, HTTP security headers, and dependency scanning.
go-error-handling
Use when writing, reviewing, or debugging Go error handling code. Covers error wrapping, sentinel errors, custom error types, error joining, single handling, and error flow patterns. Based on Google and Uber style guides.
go-performance
Use when writing, reviewing, or optimizing Go code for performance. Covers string operations, memory allocation, preallocating slices and maps, strings.Builder, strconv, container-aware GOMAXPROCS, and runtime considerations for Go 1.25.
Didn't find tool you were looking for?