Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Comprehensive Go testing patterns including table-driven tests, mocking, integration testing, benchmarks, and test organization.
.claude/skills/aiskillstore-golang-testing/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-09 | ✗→✓ | ▲ Improved | 157% | 0% |
| case-18 | ✓→✗ | ▼ Worse | 98% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 127% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 150% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 260% | 0% |
This skill provides guidance on comprehensive testing strategies for Go applications including unit tests, integration tests, benchmarks, and test organization.
gofunc TestAdd(t *testing.T) { tests := []struct { name string a, b int expected int }{ {"positive numbers", 2, 3, 5}, {"negative numbers", -2, -3, -5}, {"mixed numbers", -2, 3, 1}, {"zeros", 0, 0, 0}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := Add(tt.a, tt.b) if result != tt.expected { t.Errorf("Add(%d, %d) = %d; want %d", tt.a, tt.b, result, tt.expected) } }) } }
gofunc TestDivide(t *testing.T) { tests := []struct { name string a, b int expected int wantErr bool errString string }{ {"valid division", 10, 2, 5, false, ""}, {"divide by zero", 10, 0, 0, true, "division by zero"}, {"negative result", -10, 2, -5, false, ""}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result, err := Divide(tt.a, tt.b) if tt.wantErr { if err == nil { t.Fatalf("expected error, got nil") } if !strings.Contains(err.Error(), tt.errString) { t.Errorf("error = %v; want containing %q", err, tt.errString) } return } if err != nil { t.Fatalf("unexpected error: %v", err) } if result != tt.expected { t.Errorf("Divide(%d, %d) = %d; want %d", tt.a, tt.b, result, tt.expected) } }) } }
go// repository.go type UserRepository interface { FindByID(ctx context.Context, id string) (*User, error) Save(ctx context.Context, user *User) error } type EmailSender interface { Send(ctx context.Context, to, subject, body string) error }
go// mocks/user_repository.go type MockUserRepository struct { FindByIDFunc func(ctx context.Context, id string) (*User, error) SaveFunc func(ctx context.Context, user *User) error } func (m *MockUserRepository) FindByID(ctx context.Context, id string) (*User, error) { if m.FindByIDFunc != nil { return m.FindByIDFunc(ctx, id) } return nil, nil } func (m *MockUserRepository) Save(ctx context.Context, user *User) error { if m.SaveFunc != nil { return m.SaveFunc(ctx, user) } return nil }
gofunc TestUserService_GetUser(t *testing.T) { expectedUser := &User{ID: "123", Name: "John"} repo := &MockUserRepository{ FindByIDFunc: func(ctx context.Context, id string) (*User, error) { if id == "123" { return expectedUser, nil } return nil, ErrNotFound }, } service := NewUserService(repo) t.Run("existing user", func(t *testing.T) { user, err := service.GetUser(context.Background(), "123") if err != nil { t.Fatalf("unexpected error: %v", err) } if user.Name != expectedUser.Name { t.Errorf("got name %q; want %q", user.Name, expectedUser.Name) } }) t.Run("non-existing user", func(t *testing.T) { _, err := service.GetUser(context.Background(), "456") if !errors.Is(err, ErrNotFound) { t.Errorf("got error %v; want ErrNotFound", err) } }) }
goimport ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestWithTestify(t *testing.T) { // assert continues on failure assert.Equal(t, 5, Add(2, 3), "addition should work") assert.NotNil(t, result) assert.Len(t, items, 3) assert.Contains(t, slice, item) assert.True(t, condition) assert.NoError(t, err) assert.ErrorIs(t, err, ErrNotFound) // require stops test on failure require.NoError(t, err, "setup must succeed") require.NotNil(t, config) }
goimport ( "context" "testing" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/postgres" ) func TestUserRepository_Integration(t *testing.T) { if testing.Short() { t.Skip("skipping integration test in short mode") } ctx := context.Background() // Start PostgreSQL container pgContainer, err := postgres.Run(ctx, "postgres:15-alpine", postgres.WithDatabase("testdb"), postgres.WithUsername("test"), postgres.WithPassword("test"), ) require.NoError(t, err) defer pgContainer.Terminate(ctx) // Get connection string connStr, err := pgContainer.ConnectionString(ctx, "sslmode=disable") require.NoError(t, err) // Connect and run migrations db, err := sql.Open("postgres", connStr) require.NoError(t, err) defer db.Close() runMigrations(db) // Create repository and test repo := NewUserRepository(db) t.Run("save and find user", func(t *testing.T) { user := &User{ID: "123", Name: "John", Email: "john@example.com"} err := repo.Save(ctx, user) require.NoError(t, err) found, err := repo.FindByID(ctx, "123") require.NoError(t, err) assert.Equal(t, user.Name, found.Name) }) }
gofunc TestMain(m *testing.M) { // Global setup setup() code := m.Run() // Global teardown teardown() os.Exit(code) } func setup() { // Initialize test database, load fixtures, etc. } func teardown() { // Clean up resources }
gofunc setupTest(t *testing.T) (*UserService, func()) { t.Helper() db := setupTestDB(t) repo := NewUserRepository(db) service := NewUserService(repo) cleanup := func() { db.Close() } return service, cleanup } func TestUserService(t *testing.T) { service, cleanup := setupTest(t) defer cleanup() // Run tests using service }
gofunc BenchmarkFibonacci(b *testing.B) { for i := 0; i < b.N; i++ { Fibonacci(20) } } func BenchmarkFibonacciParallel(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { Fibonacci(20) } }) } // With sub-benchmarks func BenchmarkSort(b *testing.B) { sizes := []int{100, 1000, 10000} for _, size := range sizes { b.Run(fmt.Sprintf("size-%d", size), func(b *testing.B) { data := generateData(size) b.ResetTimer() for i := 0; i < b.N; i++ { sort.Ints(data) } }) } }
gofunc TestHandler_GetUser(t *testing.T) { // Setup mock service service := &MockUserService{ GetUserFunc: func(ctx context.Context, id string) (*User, error) { return &User{ID: id, Name: "John"}, nil }, } handler := NewHandler(service) t.Run("success", func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/users/123", nil) rec := httptest.NewRecorder() handler.GetUser(rec, req) assert.Equal(t, http.StatusOK, rec.Code) var response User err := json.NewDecoder(rec.Body).Decode(&response) require.NoError(t, err) assert.Equal(t, "John", response.Name) }) t.Run("not found", func(t *testing.T) { service.GetUserFunc = func(ctx context.Context, id string) (*User, error) { return nil, ErrNotFound } req := httptest.NewRequest(http.MethodGet, "/users/999", nil) rec := httptest.NewRecorder() handler.GetUser(rec, req) assert.Equal(t, http.StatusNotFound, rec.Code) }) }
text/internal /user user.go user_test.go # Unit tests user_integration_test.go # Integration tests (build tag) testdata/ # Test fixtures users.json
go//go:build integration package user func TestIntegration(t *testing.T) { // Integration test code }
Run with: go test -tags=integration ./...
bash# Generate coverage go test -coverprofile=coverage.out ./... # View in browser go tool cover -html=coverage.out # Check coverage percentage go test -cover ./...
t.Parallel() for independent testsTestUserService_CreateUser_WithInvalidEmail-short| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 18,422 | 12,821 | -30% | 1 | 1 | 0% | 3,651 | 5,307 | +45% | 0 | 0 | — |
case-02 | pass→pass | 10,229 | 5,931 | -42% | 1 | 1 | 0% | 1,726 | 3,918 | +127% | 0 | 0 | — |
case-03 | pass→pass | 12,748 | 15,284 | +20% | 1 | 1 | 0% | 2,425 | 6,065 | +150% | 0 | 0 | — |
case-04 | pass→pass | 7,286 | 8,014 | +10% | 1 | 1 | 0% | 1,182 | 4,257 | +260% | 0 | 0 | — |
case-05 | pass→pass | 12,131 | 10,099 | -17% | 1 | 1 | 0% | 2,569 | 4,958 | +93% | 0 | 0 | — |
case-06 | fail→fail | 16,698 | 14,321 | -14% | 1 | 1 | 0% | 2,833 | 5,414 | +91% | 0 | 0 | — |
case-07 | pass→pass | 9,696 | 6,095 | -37% | 1 | 1 | 0% | 1,721 | 3,934 | +129% | 0 | 0 | — |
case-08 | pass→pass | 8,494 | 8,275 | -3% | 1 | 1 | 0% | 1,490 | 4,389 | +195% | 0 | 0 | — |
case-09 | fail→pass | 9,537 | 8,102 | -15% | 1 | 1 | 0% | 1,584 | 4,067 | +157% | 0 | 0 | — |
case-10 | pass→pass | 4,678 | 4,374 | -6% | 1 | 1 | 0% | 789 | 3,632 | +360% | 0 | 0 | — |
case-11 | pass→pass | 9,088 | 11,427 | +26% | 1 | 1 | 0% | 1,557 | 4,303 | +176% | 0 | 0 | — |
case-12 | pass→pass | 9,069 | 9,919 | +9% | 1 | 1 | 0% | 1,656 | 4,692 | +183% | 0 | 0 | — |
case-13 | pass→pass | 8,094 | 10,082 | +25% | 1 | 1 | 0% | 1,750 | 4,866 | +178% | 0 | 0 | — |
case-14 | pass→pass | 11,685 | 10,186 | -13% | 1 | 1 | 0% | 1,930 | 4,692 | +143% | 0 | 0 | — |
case-15 | pass→pass | 7,044 | 2,615 | -63% | 1 | 1 | 0% | 648 | 3,327 | +413% | 0 | 0 | — |
case-16 | pass→pass | 6,085 | 3,211 | -47% | 1 | 1 | 0% | 984 | 3,406 | +246% | 0 | 0 | — |
case-17 | pass→pass | 3,591 | 1,927 | -46% | 1 | 1 | 0% | 586 | 3,216 | +449% | 0 | 0 | — |
case-18 | pass→fail | 11,037 | 6,117 | -45% | 1 | 1 | 0% | 2,005 | 3,962 | +98% | 0 | 0 | — |
case-19 | pass→pass | 9,281 | 4,451 | -52% | 1 | 1 | 0% | 1,605 | 3,718 | +132% | 0 | 0 | — |
case-20 | pass→pass | 15,416 | 8,765 | -43% | 1 | 1 | 0% | 2,760 | 4,590 | +66% | 0 | 0 | — |
case-21 | pass→pass | 13,486 | 12,140 | -10% | 1 | 1 | 0% | 2,676 | 5,384 | +101% | 0 | 0 | — |
case-22 | pass→pass | 6,660 | 3,697 | -44% | 1 | 1 | 0% | 1,176 | 3,511 | +199% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of 0 percentage points is the difference between those two pass rates over the 22 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.