본문으로 건너뛰기
cccskills
무료GitHub에 공개한국어 소개

golang-testing

테스트를 먼저 작성하는 Go 개발을 돕습니다. 여러 사례 검증, HTTP 응답 확인, 성능 측정, 입력 자동 생성 검사와 커버리지 확인을 다룹니다.

원문원문 설명 보기

Go testing patterns including table-driven tests, subtests, benchmarks, fuzzing, and test coverage. Follows TDD methodology with idiomatic Go practices. Use when writing Go tests — table-driven cases, subtests, benchmarks, fuzzing, or coverage.

AI 평가90점/ 100설치 방법 보기

이럴 때 유용합니다

  • 테스트를 먼저 작성하는 Go 개발
  • 정상 및 오류 테스트 사례 보완
  • 처리 시간과 메모리 할당량 비교
  • 퍼징으로 예상 밖 입력 검사

한국어 소개

할 수 있는 일

Go 코드의 동작을 확인하는 테스트와 성능을 측정하는 벤치마크를 추가합니다. 입력과 예상 결과를 표로 정리하는 테스트, 관련 사례를 나누는 하위 테스트, HTTP 응답 검증, 의존 대상을 대신하는 모의 구현 예시를 제공합니다. 입력을 자동 생성해 검사하는 퍼징과 테스트가 실행한 코드 범위를 보여 주는 커버리지 확인도 다룹니다.

이럴 때 유용합니다

테스트를 먼저 작성하고 실패를 확인한 뒤 구현하는 테스트 주도 개발인 TDD를 도입할 때 유용합니다. 기존 코드에 오류 사례를 보완하거나 처리 시간과 메모리 할당량을 비교할 때도 사용합니다. 준비 작업 공통화, 임시 파일 관리, 테스트 종료 시 정리 등 유지보수하기 쉬운 테스트 작성법도 확인합니다.

사용 예시

  • “이 Go 함수의 정상 및 오류 사례를 포함하는 테스트를 먼저 작성해 주세요.”
  • “문자열 처리 벤치마크와 입력 검증용 퍼징을 추가해 주세요.”

주의 사항

실행하려면 Go 환경이 필요합니다. 표준 퍼징 기능은 Go 1.18 이상을 대상으로 합니다. 병렬 실행은 서로 독립적인 테스트에 사용하며, 테스트용 연결과 파일은 종료 시 정리하는 방침입니다. GitHub Actions 실행 예시도 포함되어 있습니다.

이 소개문은 공개된 SKILL.md를 바탕으로 AI(OpenAI Codex)가 작성했습니다. 정확한 사양은 아래 원문을 확인하세요.

객관 평가

AI가 SKILL.md의 내용만을 근거로 4가지 관점에서 판정했습니다. 스타 수나 인기도는 판정에 넣지 않았습니다.

지시의 명확성언제 쓰고 무엇을 하는지 알 수 있는가
A양호Go 테스트 추가, 성능 측정, 입력 검증, TDD의 사용 시점을 명시합니다.
절차의 구체성절차·입출력·예시가 구체적으로 적혀 있는가
A양호TDD 단계, go test 결과, 표 기반 테스트와 임시 파일 처리 예시가 있습니다.
안전성전송·삭제·명령 실행 등을 명시하고 있는가
A양호임시 파일은 t.TempDir로 관리하며 예상 출력 파일 갱신에는 명시적인 플래그가 필요합니다.
도입의 용이성필요한 도구나 API 키가 적고 적혀 있는가
B일부 부족go test 실행 예시는 있지만 sqlite3 예시에 필요한 의존성 설치 방법은 없습니다.

총점은 각 항목을 A=25점, B=15점, C=5점으로 더한 값입니다. 2026년 10월 19일 기준으로 AI(OpenAI Codex)가 판정했습니다. 참고용이므로 설치 전에 원문도 확인해 주세요.

평가가 다르다고 생각되면 재검토를 요청하기

어느 항목이 왜 다르다고 생각하는지 적어 주세요. 로그인은 필요 없습니다. SKILL.md와 대조해 평가를 다시 만들고 이곳에 반영합니다.

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

포함된 파일(1)

  • SKILL.md16.4 KB

SKILL.md(원문)

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

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 first
GREEN   → Write minimal code to pass the test
REFACTOR → Improve code while keeping tests green
REPEAT  → Continue with next requirement

Step-by-Step TDD in Go

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

Table-Driven Tests

The standard pattern for Go tests. Enables comprehensive coverage with minimal code.

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

Table-Driven Tests with Error Cases

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

Subtests and Sub-benchmarks

Organizing Related Tests

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

Parallel Subtests

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

Test Helpers

Helper Functions

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

Temporary Files and Directories

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
}

Golden Files

Testing against expected output files stored in 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)
            }
        })
    }
}

Mocking with Interfaces

Interface-Based Mocking

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

Benchmarks

Basic Benchmarks

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

Benchmark with Different Sizes

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

Memory Allocation Benchmarks

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

Fuzzing (Go 1.18+)

Basic Fuzz Test

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

Fuzz Test with Multiple Inputs

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

Test Coverage

Running Coverage

# 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 ./...

Coverage Targets

Code TypeTarget
Critical business logic100%
Public APIs90%+
General code80%+
Generated codeExclude

Excluding Generated Code from Coverage

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

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

Testing Commands

# 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 ./...

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

# 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}'

Remember: Tests are documentation. They show how your code is meant to be used. Write them clearly and keep them up to date.

리뷰

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

댓글

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

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

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

0 / 1,000자

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

같은 저장소의 스킬

accessibility

무료요약 있음AI 평가 100점

Web·iOS·Android 화면을 WCAG 2.2에 맞춰 설계하고 점검합니다. 화면 읽기와 키보드 조작, 레이블, 대비, 조작 대상 크기를 확인합니다.

  • 아이콘 버튼에 설명 추가
  • 키보드 조작과 모달 초점 점검
  • 대비와 조작 대상 크기 확인
affaan-m/ECC28.4만2026년 10월 16일 업데이트

agent-architecture-audit

무료요약 있음AI 평가 80점

인공지능 에이전트의 문제를 지시, 기억, 도구 실행, 화면 표시 등 12개 계층에서 조사합니다. 코드와 로그를 근거로 원인을 정리하고 심각도에 따른 지적 사항과 수정안을 제시합니다.

  • 앱 안에서만 발생하는 문제 조사
  • 이전 대화가 섞이는 원인 확인
  • 도구 실행 누락과 허위 실행 보고 확인
affaan-m/ECC28.4만2026년 10월 16일 업데이트

agent-eval

무료요약 있음AI 평가 80점

실제 개발 과제로 여러 코딩 에이전트를 비교합니다. 성공률, 확인 가능한 API 비용, 소요 시간과 반복 실행의 안정성을 측정해 도입 결정과 업데이트 후 평가에 활용합니다.

  • 실제 개발 과제로 에이전트를 비교합니다
  • 새 도구나 모델을 도입하기 전에 평가합니다
  • 에이전트 업데이트 후 성능을 확인합니다
affaan-m/ECC28.4만2026년 10월 16일 업데이트

agent-harness-construction

무료요약 있음AI 평가 100점

AI 에이전트의 도구 구성, 입력과 출력 형식, 오류 복구 절차를 설계합니다. 문맥 정보량을 관리하고 완료율, 재시도 횟수, 성공한 작업당 비용으로 개선을 평가합니다.

  • 에이전트 도구와 입력 형식을 설계합니다
  • 도구 결과와 다음 행동을 명확히 합니다
  • 안전한 재시도와 중단 조건을 정합니다
affaan-m/ECC28.4만2026년 10월 16일 업데이트

agent-introspection-debugging

무료요약 있음AI 평가 100점

오류와 실행 환경을 바탕으로 AI 에이전트가 실패하거나 같은 작업을 반복하는 원인을 조사합니다. 작은 복구 작업을 시험하고 결과와 근거를 인계용 보고서로 정리합니다.

  • 에이전트의 연속 실패 진단
  • 동일한 도구 작업의 반복 조사
  • 정보 누적으로 인한 출력 품질 저하 점검
affaan-m/ECC28.4만2026년 10월 5일 업데이트

agent-introspection-debugging

무료요약 있음AI 평가 100점

AI 에이전트의 반복 실패와 도구 사용을 기록하고 원인을 좁힙니다. 영향이 제한된 복구 작업과 결과 보고를 통해 근거 있는 재시도를 돕습니다.

  • 같은 도구 작업을 반복하는 원인 조사
  • 대화 증가에 따른 출력 품질 저하 조사
  • 파일 경로와 환경 불일치 확인
affaan-m/ECC28.4만2026년 10월 16일 업데이트

affaan-m의 스킬 모두 보기

이 스킬의 문제 신고