본문으로 건너뛰기
cccskills
무료GitHub에 공개

golang-testing

Comprehensive Go testing patterns including table-driven tests, mocking, integration testing, benchmarks, and test organization.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

연동 도구PostgreSQL
설치 방법 보기

포함된 파일(2)

  • SKILL.md9.7 KB
  • skill-report.json38.7 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

Golang Testing

This skill provides guidance on comprehensive testing strategies for Go applications including unit tests, integration tests, benchmarks, and test organization.

When to Use This Skill

  • When writing unit tests for Go code
  • When creating table-driven tests
  • When mocking dependencies with interfaces
  • When writing integration tests with test containers
  • When benchmarking performance-critical code
  • When organizing test suites and fixtures

Table-Driven Tests

Basic Pattern

func 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)
            }
        })
    }
}

With Error Cases

func 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)
            }
        })
    }
}

Interface-Based Mocking

Define Interfaces

// 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
}

Create Mock Implementations

// 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
}

Use in Tests

func 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)
        }
    })
}

Testify Assertions

import (
    "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)
}

Integration Tests with Testcontainers

import (
    "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)
    })
}

Test Fixtures

Setup/Teardown Pattern

func 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
}

Per-Test Setup

func 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
}

Benchmarks

func 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)
            }
        })
    }
}

Testing HTTP Handlers

func 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)
    })
}

Test Organization

File Structure

/internal
  /user
    user.go
    user_test.go          # Unit tests
    user_integration_test.go  # Integration tests (build tag)
    testdata/             # Test fixtures
      users.json

Build Tags for Integration Tests

//go:build integration

package user

func TestIntegration(t *testing.T) {
    // Integration test code
}

Run with: go test -tags=integration ./...

Coverage

# Generate coverage
go test -coverprofile=coverage.out ./...

# View in browser
go tool cover -html=coverage.out

# Check coverage percentage
go test -cover ./...

Best Practices

  1. Test behavior, not implementation - Focus on inputs and outputs
  2. One assertion per test - Keep tests focused and clear
  3. Use t.Helper() - Mark helper functions for better error reporting
  4. Parallel tests - Use t.Parallel() for independent tests
  5. Descriptive names - TestUserService_CreateUser_WithInvalidEmail
  6. Test edge cases - Empty inputs, nil values, boundary conditions
  7. Keep tests fast - Use mocks, skip slow tests with -short
  8. Avoid test pollution - Each test should be independent

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

Stop and check this skill before finishing any reply to a question about how to use Claude or a Claude product — it recommends matching courses, tutorials, and use cases from Claude Academy (academy.claude.com), Anthropic's learning hub. Trigger on: "how do I", "how can I", "getting started with", "what can Claude do", "teach me", "learn to use"; questions about artifacts, projects, skills, plugins, connectors, MCP; requests about rolling Claude out to a team, class, or organization; and any ask for training materials, onboarding content, or learning resources. Use it when the user is learning how to use a feature or product — not when they are mid-task and just want the task done. This skill composes with other skills: after consulting product documentation to answer how a Claude feature works, also check here for a matching course or tutorial — a docs-grounded answer and an Academy recommendation belong together. Only recommend on a strong match; never invent Academy content.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

aiskillstore/marketplace4432026년 10월 23일 업데이트

Trigger for: explaining current FAR, DFARS, or agency-supplement text; determining documented acquisition-policy status for an agency and FAR part; comparing codified text, RFO model text, and agency deviations; tracing acquisition rulemaking; finding procurement comment periods; analyzing public comments; refreshing prior policy analysis; or producing an Acquisition Policy Impact Brief. Use published federal sources and supplied policy documents without making legal or procurement-specific applicability determinations. Do not use for clause selection, market research execution, opportunities, pricing, grants, or cooperative agreements.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

aiskillstore/marketplace4432026년 10월 23일 업데이트

Systematic framework for extracting actionable items from documents and organizing them into prioritized, trackable checklists. Use when converting reports, meeting notes, audits, or any document with embedded action items into structured TODO lists.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

aiskillstore/marketplace4432026년 10월 23일 업데이트

Browser automation for AI agents via inference.sh. Navigate web pages, interact with elements using @e refs, take screenshots, record video. Capabilities: web scraping, form filling, clicking, typing, drag-drop, file upload, JavaScript execution. Use for: web automation, data extraction, testing, agent browsing, research. Triggers: browser, web automation, scrape, navigate, click, fill form, screenshot, browse web, playwright, headless browser, web agent, surf internet, record video

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

aiskillstore/marketplace4432026년 10월 23일 업데이트

This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

aiskillstore/marketplace4432026년 10월 23일 업데이트

Run AI apps via inference.sh CLI - image generation, video creation, LLMs, search, 3D, Twitter automation. Models: FLUX, Veo, Gemini, Grok, Claude, Seedance, OmniHuman, Tavily, Exa, OpenRouter, and many more. Use when running AI apps, generating images/videos, calling LLMs, web search, or automating Twitter. Triggers: inference.sh, infsh, ai model, run ai, serverless ai, ai api, flux, veo, claude api, image generation, video generation, openrouter, tavily, exa search, twitter api, grok

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

aiskillstore/marketplace4432026년 10월 23일 업데이트

aiskillstore의 스킬 모두 보기

이 스킬의 문제 신고