Install
Terminal · npx$
npx skills add https://github.com/affaan-m/everything-claude-code --skill golang-testingWorks with Paperclip
How Golang Testing fits into a Paperclip company.
Golang Testing drops into any Paperclip agent that handles this kind of work. Assign it to a specialist inside a pre-configured PaperclipOrg company and the skill becomes available on every heartbeat — no prompt engineering, no tool wiring.
S
SaaS FactoryPaired
Pre-configured AI company — 18 agents, 18 skills, one-time purchase.
$27$59
Explore packSource file
SKILL.md720 linesExpandCollapse
---name: golang-testingdescription: Go testing patterns including table-driven tests, subtests, benchmarks, fuzzing, and test coverage. Follows TDD methodology with idiomatic Go practices.origin: ECC--- # Go Testing Patterns Comprehensive Go testing patterns for writing reliable, maintainable tests following TDD methodology. ## When to Activate - Writing new Go functions or methods- Adding test coverage to existing code- Creating benchmarks for performance-critical code- Implementing fuzz tests for input validation- Following TDD workflow in Go projects ## TDD Workflow for Go ### The RED-GREEN-REFACTOR Cycle ```RED → Write a failing test firstGREEN → Write minimal code to pass the testREFACTOR → Improve code while keeping tests greenREPEAT → Continue with next requirement``` ### Step-by-Step TDD in Go ```go// Step 1: Define the interface/signature// calculator.gopackage calculator func Add(a, b int) int { panic("not implemented") // Placeholder} // Step 2: Write failing test (RED)// calculator_test.gopackage calculator import "testing" func TestAdd(t *testing.T) { got := Add(2, 3) want := 5 if got != want { t.Errorf("Add(2, 3) = %d; want %d", got, want) }} // Step 3: Run test - verify FAIL// $ go test// --- FAIL: TestAdd (0.00s)// panic: not implemented // Step 4: Implement minimal code (GREEN)func Add(a, b int) int { return a + b} // Step 5: Run test - verify PASS// $ go test// PASS // Step 6: Refactor if needed, verify tests still pass``` ## Table-Driven Tests The standard pattern for Go tests. Enables comprehensive coverage with minimal code. ```gofunc TestAdd(t *testing.T) { tests := []struct { name string a, b int expected int }{ {"positive numbers", 2, 3, 5}, {"negative numbers", -1, -2, -3}, {"zero values", 0, 0, 0}, {"mixed signs", -1, 1, 0}, {"large numbers", 1000000, 2000000, 3000000}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := Add(tt.a, tt.b) if got != tt.expected { t.Errorf("Add(%d, %d) = %d; want %d", tt.a, tt.b, got, tt.expected) } }) }}``` ### Table-Driven Tests with Error Cases ```gofunc TestParseConfig(t *testing.T) { tests := []struct { name string input string want *Config wantErr bool }{ { name: "valid config", input: `{"host": "localhost", "port": 8080}`, want: &Config{Host: "localhost", Port: 8080}, }, { name: "invalid JSON", input: `{invalid}`, wantErr: true, }, { name: "empty input", input: "", wantErr: true, }, { name: "minimal config", input: `{}`, want: &Config{}, // Zero value config }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := ParseConfig(tt.input) if tt.wantErr { if err == nil { t.Error("expected error, got nil") } return } if err != nil { t.Fatalf("unexpected error: %v", err) } if !reflect.DeepEqual(got, tt.want) { t.Errorf("got %+v; want %+v", got, tt.want) } }) }}``` ## Subtests and Sub-benchmarks ### Organizing Related Tests ```gofunc TestUser(t *testing.T) { // Setup shared by all subtests db := setupTestDB(t) t.Run("Create", func(t *testing.T) { user := &User{Name: "Alice"} err := db.CreateUser(user) if err != nil { t.Fatalf("CreateUser failed: %v", err) } if user.ID == "" { t.Error("expected user ID to be set") } }) t.Run("Get", func(t *testing.T) { user, err := db.GetUser("alice-id") if err != nil { t.Fatalf("GetUser failed: %v", err) } if user.Name != "Alice" { t.Errorf("got name %q; want %q", user.Name, "Alice") } }) t.Run("Update", func(t *testing.T) { // ... }) t.Run("Delete", func(t *testing.T) { // ... })}``` ### Parallel Subtests ```gofunc TestParallel(t *testing.T) { tests := []struct { name string input string }{ {"case1", "input1"}, {"case2", "input2"}, {"case3", "input3"}, } for _, tt := range tests { tt := tt // Capture range variable t.Run(tt.name, func(t *testing.T) { t.Parallel() // Run subtests in parallel result := Process(tt.input) // assertions... _ = result }) }}``` ## Test Helpers ### Helper Functions ```gofunc setupTestDB(t *testing.T) *sql.DB { t.Helper() // Marks this as a helper function db, err := sql.Open("sqlite3", ":memory:") if err != nil { t.Fatalf("failed to open database: %v", err) } // Cleanup when test finishes t.Cleanup(func() { db.Close() }) // Run migrations if _, err := db.Exec(schema); err != nil { t.Fatalf("failed to create schema: %v", err) } return db} func assertNoError(t *testing.T, err error) { t.Helper() if err != nil { t.Fatalf("unexpected error: %v", err) }} func assertEqual[T comparable](t *testing.T, got, want T) { t.Helper() if got != want { t.Errorf("got %v; want %v", got, want) }}``` ### Temporary Files and Directories ```gofunc TestFileProcessing(t *testing.T) { // Create temp directory - automatically cleaned up tmpDir := t.TempDir() // Create test file testFile := filepath.Join(tmpDir, "test.txt") err := os.WriteFile(testFile, []byte("test content"), 0644) if err != nil { t.Fatalf("failed to create test file: %v", err) } // Run test result, err := ProcessFile(testFile) if err != nil { t.Fatalf("ProcessFile failed: %v", err) } // Assert... _ = result}``` ## Golden Files Testing against expected output files stored in `testdata/`. ```govar update = flag.Bool("update", false, "update golden files") func TestRender(t *testing.T) { tests := []struct { name string input Template }{ {"simple", Template{Name: "test"}}, {"complex", Template{Name: "test", Items: []string{"a", "b"}}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := Render(tt.input) golden := filepath.Join("testdata", tt.name+".golden") if *update { // Update golden file: go test -update err := os.WriteFile(golden, got, 0644) if err != nil { t.Fatalf("failed to update golden file: %v", err) } } want, err := os.ReadFile(golden) if err != nil { t.Fatalf("failed to read golden file: %v", err) } if !bytes.Equal(got, want) { t.Errorf("output mismatch:\ngot:\n%s\nwant:\n%s", got, want) } }) }}``` ## Mocking with Interfaces ### Interface-Based Mocking ```go// Define interface for dependenciestype UserRepository interface { GetUser(id string) (*User, error) SaveUser(user *User) error} // Production implementationtype PostgresUserRepository struct { db *sql.DB} func (r *PostgresUserRepository) GetUser(id string) (*User, error) { // Real database query} // Mock implementation for teststype MockUserRepository struct { GetUserFunc func(id string) (*User, error) SaveUserFunc func(user *User) error} func (m *MockUserRepository) GetUser(id string) (*User, error) { return m.GetUserFunc(id)} func (m *MockUserRepository) SaveUser(user *User) error { return m.SaveUserFunc(user)} // Test using mockfunc TestUserService(t *testing.T) { mock := &MockUserRepository{ GetUserFunc: func(id string) (*User, error) { if id == "123" { return &User{ID: "123", Name: "Alice"}, nil } return nil, ErrNotFound }, } service := NewUserService(mock) user, err := service.GetUserProfile("123") if err != nil { t.Fatalf("unexpected error: %v", err) } if user.Name != "Alice" { t.Errorf("got name %q; want %q", user.Name, "Alice") }}``` ## Benchmarks ### Basic Benchmarks ```gofunc BenchmarkProcess(b *testing.B) { data := generateTestData(1000) b.ResetTimer() // Don't count setup time for i := 0; i < b.N; i++ { Process(data) }} // Run: go test -bench=BenchmarkProcess -benchmem// Output: BenchmarkProcess-8 10000 105234 ns/op 4096 B/op 10 allocs/op``` ### Benchmark with Different Sizes ```gofunc BenchmarkSort(b *testing.B) { sizes := []int{100, 1000, 10000, 100000} for _, size := range sizes { b.Run(fmt.Sprintf("size=%d", size), func(b *testing.B) { data := generateRandomSlice(size) b.ResetTimer() for i := 0; i < b.N; i++ { // Make a copy to avoid sorting already sorted data tmp := make([]int, len(data)) copy(tmp, data) sort.Ints(tmp) } }) }}``` ### Memory Allocation Benchmarks ```gofunc BenchmarkStringConcat(b *testing.B) { parts := []string{"hello", "world", "foo", "bar", "baz"} b.Run("plus", func(b *testing.B) { for i := 0; i < b.N; i++ { var s string for _, p := range parts { s += p } _ = s } }) b.Run("builder", func(b *testing.B) { for i := 0; i < b.N; i++ { var sb strings.Builder for _, p := range parts { sb.WriteString(p) } _ = sb.String() } }) b.Run("join", func(b *testing.B) { for i := 0; i < b.N; i++ { _ = strings.Join(parts, "") } })}``` ## Fuzzing (Go 1.18+) ### Basic Fuzz Test ```gofunc FuzzParseJSON(f *testing.F) { // Add seed corpus f.Add(`{"name": "test"}`) f.Add(`{"count": 123}`) f.Add(`[]`) f.Add(`""`) f.Fuzz(func(t *testing.T, input string) { var result map[string]interface{} err := json.Unmarshal([]byte(input), &result) if err != nil { // Invalid JSON is expected for random input return } // If parsing succeeded, re-encoding should work _, err = json.Marshal(result) if err != nil { t.Errorf("Marshal failed after successful Unmarshal: %v", err) } })} // Run: go test -fuzz=FuzzParseJSON -fuzztime=30s``` ### Fuzz Test with Multiple Inputs ```gofunc FuzzCompare(f *testing.F) { f.Add("hello", "world") f.Add("", "") f.Add("abc", "abc") f.Fuzz(func(t *testing.T, a, b string) { result := Compare(a, b) // Property: Compare(a, a) should always equal 0 if a == b && result != 0 { t.Errorf("Compare(%q, %q) = %d; want 0", a, b, result) } // Property: Compare(a, b) and Compare(b, a) should have opposite signs reverse := Compare(b, a) if (result > 0 && reverse >= 0) || (result < 0 && reverse <= 0) { if result != 0 || reverse != 0 { t.Errorf("Compare(%q, %q) = %d, Compare(%q, %q) = %d; inconsistent", a, b, result, b, a, reverse) } } })}``` ## Test Coverage ### Running Coverage ```bash# Basic coveragego test -cover ./... # Generate coverage profilego test -coverprofile=coverage.out ./... # View coverage in browsergo tool cover -html=coverage.out # View coverage by functiongo tool cover -func=coverage.out # Coverage with race detectiongo test -race -coverprofile=coverage.out ./...``` ### Coverage Targets | Code Type | Target ||-----------|--------|| Critical business logic | 100% || Public APIs | 90%+ || General code | 80%+ || Generated code | Exclude | ### Excluding Generated Code from Coverage ```go//go:generate mockgen -source=interface.go -destination=mock_interface.go // In coverage profile, exclude with build tags:// go test -cover -tags=!generate ./...``` ## HTTP Handler Testing ```gofunc TestHealthHandler(t *testing.T) { // Create request req := httptest.NewRequest(http.MethodGet, "/health", nil) w := httptest.NewRecorder() // Call handler HealthHandler(w, req) // Check response resp := w.Result() defer resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Errorf("got status %d; want %d", resp.StatusCode, http.StatusOK) } body, _ := io.ReadAll(resp.Body) if string(body) != "OK" { t.Errorf("got body %q; want %q", body, "OK") }} func TestAPIHandler(t *testing.T) { tests := []struct { name string method string path string body string wantStatus int wantBody string }{ { name: "get user", method: http.MethodGet, path: "/users/123", wantStatus: http.StatusOK, wantBody: `{"id":"123","name":"Alice"}`, }, { name: "not found", method: http.MethodGet, path: "/users/999", wantStatus: http.StatusNotFound, }, { name: "create user", method: http.MethodPost, path: "/users", body: `{"name":"Bob"}`, wantStatus: http.StatusCreated, }, } handler := NewAPIHandler() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var body io.Reader if tt.body != "" { body = strings.NewReader(tt.body) } req := httptest.NewRequest(tt.method, tt.path, body) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() handler.ServeHTTP(w, req) if w.Code != tt.wantStatus { t.Errorf("got status %d; want %d", w.Code, tt.wantStatus) } if tt.wantBody != "" && w.Body.String() != tt.wantBody { t.Errorf("got body %q; want %q", w.Body.String(), tt.wantBody) } }) }}``` ## Testing Commands ```bash# Run all testsgo test ./... # Run tests with verbose outputgo test -v ./... # Run specific testgo test -run TestAdd ./... # Run tests matching patterngo test -run "TestUser/Create" ./... # Run tests with race detectorgo test -race ./... # Run tests with coveragego test -cover -coverprofile=coverage.out ./... # Run short tests onlygo test -short ./... # Run tests with timeoutgo test -timeout 30s ./... # Run benchmarksgo test -bench=. -benchmem ./... # Run fuzzinggo test -fuzz=FuzzParse -fuzztime=30s ./... # Count test runs (for flaky test detection)go test -count=10 ./...``` ## Best Practices **DO:**- Write tests FIRST (TDD)- Use table-driven tests for comprehensive coverage- Test behavior, not implementation- Use `t.Helper()` in helper functions- Use `t.Parallel()` for independent tests- Clean up resources with `t.Cleanup()`- Use meaningful test names that describe the scenario **DON'T:**- Test private functions directly (test through public API)- Use `time.Sleep()` in tests (use channels or conditions)- Ignore flaky tests (fix or remove them)- Mock everything (prefer integration tests when possible)- Skip error path testing ## Integration with CI/CD ```yaml# GitHub Actions exampletest: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: go-version: '1.22' - name: Run tests run: go test -race -coverprofile=coverage.out ./... - name: Check coverage run: | go tool cover -func=coverage.out | grep total | awk '{print $3}' | \ awk -F'%' '{if ($1 < 80) exit 1}'``` **Remember**: Tests are documentation. They show how your code is meant to be used. Write them clearly and keep them up to date.Related skills
Agent Eval
Install Agent Eval skill for Claude Code from affaan-m/everything-claude-code.
Agent Harness Construction
Install Agent Harness Construction skill for Claude Code from affaan-m/everything-claude-code.
Agent Payment X402
Install Agent Payment X402 skill for Claude Code from affaan-m/everything-claude-code.