Source profileQuality 81/100Review permissions

affaan-m/ECC/docs/ko-KR/skills/golang-testing/SKILL.md

golang-testing

Use it for testing and engineering tasks; the detail page covers purpose, installation, and practical steps.

Source repository stars
234,327
Declared platforms
0
Static risk flags
3
Last source update
2026-07-27
Source checked
2026-07-28

Decision brief

What it does—and where it fits

TDD 방법론을 따르는 신뢰할 수 있고 유지보수 가능한 테스트 작성을 위한 포괄적인 Go 테스팅 패턴.

Best for

    Not for

    • Tasks that require unconfirmed production actions or broad system permissions.
    • Environments where the pinned source and install steps cannot be inspected.

    Compatibility matrix

    Platform support, with evidence labels

    PlatformStatusEvidenceWhat to check
    CodexNot declaredNo explicit evidencePortability before use
    Claude CodeNot declaredNo explicit evidencePortability before use
    CursorNot declaredNo explicit evidencePortability before use
    Gemini CLINot declaredNo explicit evidencePortability before use
    Open the compatibility checker

    Installation

    Inspect first. Install second.

    The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.

    Source-detected install commandSource
    npx skills add https://github.com/affaan-m/ECC --skill "docs/ko-KR/skills/golang-testing"
    Safe inspection promptEditorial

    Inspect the Agent Skill "golang-testing" from https://github.com/affaan-m/ECC/blob/4e973d3eaf92d97f8d2e2d8abb39d8bdc8711b38/docs/ko-KR/skills/golang-testing/SKILL.md at commit 4e973d3eaf92d97f8d2e2d8abb39d8bdc8711b38. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.

    Workflow

    What the source asks the agent to do

    1. 01

      활성화 시점

      새로운 Go 함수나 메서드 작성 시

      새로운 Go 함수나 메서드 작성 시기존 코드에 테스트 커버리지 추가 시성능이 중요한 코드에 벤치마크 생성 시
    2. 02

      Go에서의 TDD 워크플로우

      Review the “Go에서의 TDD 워크플로우” section in the pinned source before continuing.

      Review and apply the “Go에서의 TDD 워크플로우” source section.
    3. 03

      RED-GREEN-REFACTOR 사이클

      Review the “RED-GREEN-REFACTOR 사이클” section in the pinned source before continuing.

      Review and apply the “RED-GREEN-REFACTOR 사이클” source section.
    4. 04

      Go에서의 단계별 TDD

      Review the “Go에서의 단계별 TDD” section in the pinned source before continuing.

      Review and apply the “Go에서의 단계별 TDD” source section.
    5. 05

      테이블 주도 테스트

      Go 테스트의 표준 패턴. 최소한의 코드로 포괄적인 커버리지를 가능하게 합니다.

      Go 테스트의 표준 패턴. 최소한의 코드로 포괄적인 커버리지를 가능하게 합니다.

    Permission review

    Static risk signals and limitations

    Writes files

    medium · line 261

    The documentation asks the agent to create, modify, or delete local files.

    // Create temp directory - automatically cleaned up

    Writes files

    medium · line 264

    The documentation asks the agent to create, modify, or delete local files.

    // Create test file

    Reads files

    low · line 312

    The documentation asks the agent to read local files, directories, or repositories.

    want, err := os.ReadFile(golden)

    Reads files

    low · line 314

    The documentation asks the agent to read local files, directories, or repositories.

    t.Fatalf("failed to read golden file: %v", err)

    Runs scripts

    medium · line 522

    The documentation asks the agent to run terminal commands or scripts.

    go test -cover ./...

    Runs scripts

    medium · line 525

    The documentation asks the agent to run terminal commands or scripts.

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

    Evidence record

    Why each signal appears

    EvidenceSourceComputedTestedEditorial
    SignalValueEvidence typeMeaning
    Quality score81/100ComputedDocumentation, specificity, maintenance, and trust rules
    Repository stars234,327SourceRepository attention, not individual Skill quality
    Compatibility0 platformsSourceDeclared in the catalog source record
    Usage guideautomated source guideEditorialGenerated or reviewed according to the visible evidence level

    Pinned source

    Provenance and original SKILL.md

    Repository
    affaan-m/ECC
    Skill path
    docs/ko-KR/skills/golang-testing/SKILL.md
    Commit
    4e973d3eaf92d97f8d2e2d8abb39d8bdc8711b38
    License
    MIT
    Collected
    2026-07-28
    Default branch
    main
    View the original SKILL.md

    Go 테스팅 패턴

    TDD 방법론을 따르는 신뢰할 수 있고 유지보수 가능한 테스트 작성을 위한 포괄적인 Go 테스팅 패턴.

    활성화 시점

    • 새로운 Go 함수나 메서드 작성 시
    • 기존 코드에 테스트 커버리지 추가 시
    • 성능이 중요한 코드에 벤치마크 생성 시
    • 입력 유효성 검사를 위한 퍼즈 테스트 구현 시
    • Go 프로젝트에서 TDD 워크플로우 따를 시

    Go에서의 TDD 워크플로우

    RED-GREEN-REFACTOR 사이클

    RED     → Write a failing test first
    GREEN   → Write minimal code to pass the test
    REFACTOR → Improve code while keeping tests green
    REPEAT  → Continue with next requirement
    

    Go에서의 단계별 TDD

    // Step 1: Define the interface/signature
    // calculator.go
    package calculator
    
    func Add(a, b int) int {
        panic("not implemented") // Placeholder
    }
    
    // Step 2: Write failing test (RED)
    // calculator_test.go
    package 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
    

    테이블 주도 테스트

    Go 테스트의 표준 패턴. 최소한의 코드로 포괄적인 커버리지를 가능하게 합니다.

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

    에러 케이스가 있는 테이블 주도 테스트

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

    서브테스트 및 서브벤치마크

    관련 테스트 구성

    func 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) {
            // ...
        })
    }
    

    병렬 서브테스트

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

    테스트 헬퍼

    헬퍼 함수

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

    임시 파일 및 디렉터리

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

    골든 파일

    testdata/에 저장된 예상 출력 파일에 대한 테스트.

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

    인터페이스를 사용한 모킹

    인터페이스 기반 모킹

    // Define interface for dependencies
    type UserRepository interface {
        GetUser(id string) (*User, error)
        SaveUser(user *User) error
    }
    
    // Production implementation
    type PostgresUserRepository struct {
        db *sql.DB
    }
    
    func (r *PostgresUserRepository) GetUser(id string) (*User, error) {
        // Real database query
    }
    
    // Mock implementation for tests
    type 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 mock
    func 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")
        }
    }
    

    벤치마크

    기본 벤치마크

    func 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
    

    다양한 크기의 벤치마크

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

    메모리 할당 벤치마크

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

    퍼징 (Go 1.18+)

    기본 퍼즈 테스트

    func 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
    

    다중 입력 퍼즈 테스트

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

    테스트 커버리지

    커버리지 실행

    # Basic coverage
    go test -cover ./...
    
    # Generate coverage profile
    go test -coverprofile=coverage.out ./...
    
    # View coverage in browser
    go tool cover -html=coverage.out
    
    # View coverage by function
    go tool cover -func=coverage.out
    
    # Coverage with race detection
    go test -race -coverprofile=coverage.out ./...
    

    커버리지 목표

    코드 유형목표
    핵심 비즈니스 로직100%
    공개 API90%+
    일반 코드80%+
    생성된 코드제외

    생성된 코드를 커버리지에서 제외

    //go:generate mockgen -source=interface.go -destination=mock_interface.go
    
    // In coverage profile, exclude with build tags:
    // go test -cover -tags=!generate ./...
    

    HTTP 핸들러 테스팅

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

    테스팅 명령어

    # Run all tests
    go test ./...
    
    # Run tests with verbose output
    go test -v ./...
    
    # Run specific test
    go test -run TestAdd ./...
    
    # Run tests matching pattern
    go test -run "TestUser/Create" ./...
    
    # Run tests with race detector
    go test -race ./...
    
    # Run tests with coverage
    go test -cover -coverprofile=coverage.out ./...
    
    # Run short tests only
    go test -short ./...
    
    # Run tests with timeout
    go test -timeout 30s ./...
    
    # Run benchmarks
    go test -bench=. -benchmem ./...
    
    # Run fuzzing
    go test -fuzz=FuzzParse -fuzztime=30s ./...
    
    # Count test runs (for flaky test detection)
    go test -count=10 ./...
    

    모범 사례

    해야 할 것:

    • 테스트를 먼저 작성 (TDD)
    • 포괄적인 커버리지를 위해 테이블 주도 테스트 사용
    • 구현이 아닌 동작을 테스트
    • 헬퍼 함수에서 t.Helper() 사용
    • 독립적인 테스트에 t.Parallel() 사용
    • t.Cleanup()으로 리소스 정리
    • 시나리오를 설명하는 의미 있는 테스트 이름 사용

    하지 말아야 할 것:

    • 비공개 함수를 직접 테스트 (공개 API를 통해 테스트)
    • 테스트에서 time.Sleep() 사용 (채널이나 조건 사용)
    • 불안정한 테스트 무시 (수정하거나 제거)
    • 모든 것을 모킹 (가능하면 통합 테스트 선호)
    • 에러 경로 테스트 생략

    CI/CD 통합

    # GitHub Actions example
    test:
      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}'
    

    기억하세요: 테스트는 문서입니다. 코드가 어떻게 사용되어야 하는지를 보여줍니다. 명확하게 작성하고 최신 상태로 유지하세요.

    Alternatives

    Compare before choosing