affaan-m/ECC

golang-testing

Go testing patterns including table-driven tests, subtests, benchmarks, fuzzing, and test coverage. Follows TDD methodology with idiomatic Go practices.

67CollectingWrites filesReads filesRuns scripts
See how to use itView GitHub source
npx skills add https://github.com/affaan-m/ECC --skill "docs/zh-TW/skills/golang-testing"
Automated source guide

Source checked Jul 28, 2026·Refresh due Oct 26, 2026

Reorganized from the pinned upstream SKILL.md

Turn golang-testing's source instructions into a guide you can follow

According to the pinned SKILL.md from affaan-m/ECC: 用於撰寫可靠、可維護測試的完整 Go 測試模式,遵循 TDD 方法論。

npx skills add https://github.com/affaan-m/ECC --skill "docs/zh-TW/skills/golang-testing"
Check the pinned source

Best fit

  • Go testing patterns including table-driven tests, subtests, benchmarks, fuzzing, and test coverage. Follows TDD methodology with idiomatic Go practices.

Bring this context

  • A concrete task that matches the documented purpose of golang-testing.
  • The files, examples, or context the task depends on.
  • Your constraints, target environment, and definition of done.

Expected outputs

  • A result that follows the pinned golang-testing instructions.
  • A concise record of assumptions, inputs used, and unresolved questions.
  • A final check against the source workflow and relevant permission signals.

Key source sections

Read golang-testing through these 5 source sections

Sections are extracted automatically from the pinned SKILL.md and link back to the source.

01

何時啟用

撰寫新的 Go 函式或方法

SKILL.md · 何時啟用
撰寫新的 Go 函式或方法為現有程式碼增加測試覆蓋率為效能關鍵程式碼建立基準測試
02

Go 的 TDD 工作流程

Review the “Go 的 TDD 工作流程” section in the pinned source before continuing.

SKILL.md · Go 的 TDD 工作流程
Review and apply the “Go 的 TDD 工作流程” source section.
03

RED-GREEN-REFACTOR 循環

Review the “RED-GREEN-REFACTOR 循環” section in the pinned source before continuing.

SKILL.md · RED-GREEN-REFACTOR 循環
Review and apply the “RED-GREEN-REFACTOR 循環” source section.
04

Go 中的逐步 TDD

Review the “Go 中的逐步 TDD” section in the pinned source before continuing.

SKILL.md · Go 中的逐步 TDD
Review and apply the “Go 中的逐步 TDD” source section.
05

表格驅動測試

Go 測試的標準模式。以最少程式碼達到完整覆蓋。

SKILL.md · 表格驅動測試
Go 測試的標準模式。以最少程式碼達到完整覆蓋。

SkillSignal prompt templates

Provide the task, context, and acceptance criteria

These prompts were written by SkillSignal from the source structure; they are not upstream text.

Task-start prompt

Confirm source fit, inputs, and outputs before acting.

Use golang-testing to help me with: [specific task]. Context: [files, data, or background]. Constraints: [environment, scope, and prohibited actions]. Before acting, check the pinned SKILL.md and explain which sections apply, what inputs are still missing, and what you will deliver.

Source-guided execution

Make the Agent explicitly follow the key extracted sections.

Apply the pinned golang-testing source to [task]. Pay particular attention to these source sections: “何時啟用”, “Go 的 TDD 工作流程”, “RED-GREEN-REFACTOR 循環”, “Go 中的逐步 TDD”, “表格驅動測試”. Preserve the important decision at each step. Mark facts not covered by the source as “needs confirmation” instead of inventing them. Then verify the result against my acceptance criteria: [criteria].

Result-review prompt

Check omissions, permissions, and source drift before delivery.

Review the current golang-testing result: (1) does it satisfy the original task; (2) were any applicable steps or limits in the pinned SKILL.md missed; (3) did it perform any unauthorized file, command, network, or data action; and (4) which conclusions remain unverified? List issues first, then fix only what the source or user authorization supports.

Output checklist

Verify each item before delivery

The task matches the purpose documented in the SKILL.md.

The source section “何時啟用” has been checked.

The source section “Go 的 TDD 工作流程” has been checked.

The source section “RED-GREEN-REFACTOR 循環” has been checked.

The source section “Go 中的逐步 TDD” has been checked.

Inputs, constraints, and acceptance criteria are explicit.

Unverified facts, compatibility, and outcome claims are clearly marked.

Any file, command, network, or data action has been reviewed.

Choose a different workflow

When another Skill is the better fit

FAQ

What does golang-testing do?

用於撰寫可靠、可維護測試的完整 Go 測試模式,遵循 TDD 方法論。

How do I start using golang-testing?

The catalog detected this source-specific install command: npx skills add https://github.com/affaan-m/ECC --skill "docs/zh-TW/skills/golang-testing". Inspect the command and pinned source before running it.

Which Agent platforms does it declare?

No dedicated Agent platform is declared in the pinned source record.

Repository stars
234,327
Repository forks
35,711
Quality
67/100
Source repository last pushed

Quality breakdown

Based on traceable docs and repository signals; stars are not treated as quality.

67/100
Documentation25/30
Specificity16/25
Maintenance18/20
Trust signals8/25
View original Skill.mdThis page is parsed directly from the repository SKILL.md without editorial rewriting. Collected: Jul 28, 2026 · about 1 min

Go 測試模式

用於撰寫可靠、可維護測試的完整 Go 測試模式,遵循 TDD 方法論。

何時啟用

  • 撰寫新的 Go 函式或方法
  • 為現有程式碼增加測試覆蓋率
  • 為效能關鍵程式碼建立基準測試
  • 實作輸入驗證的模糊測試
  • 在 Go 專案中遵循 TDD 工作流程

Go 的 TDD 工作流程

RED-GREEN-REFACTOR 循環

RED     → 先寫失敗的測試
GREEN   → 撰寫最少程式碼使測試通過
REFACTOR → 在保持測試綠色的同時改善程式碼
REPEAT  → 繼續下一個需求

Go 中的逐步 TDD

// 步驟 1:定義介面/簽章
// calculator.go
package calculator

func Add(a, b int) int {
    panic("not implemented") // 佔位符
}

// 步驟 2:撰寫失敗測試(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)
    }
}

// 步驟 3:執行測試 - 驗證失敗
// $ go test
// --- FAIL: TestAdd (0.00s)
// panic: not implemented

// 步驟 4:實作最少程式碼(GREEN)
func Add(a, b int) int {
    return a + b
}

// 步驟 5:執行測試 - 驗證通過
// $ go test
// PASS

// 步驟 6:如需要則重構,驗證測試仍然通過

表格驅動測試

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{}, // 零值 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) {
    // 所有子測試共享的設置
    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 // 捕獲範圍變數
        t.Run(tt.name, func(t *testing.T) {
            t.Parallel() // 並行執行子測試
            result := Process(tt.input)
            // 斷言...
            _ = result
        })
    }
}

測試輔助函式

輔助函式

func setupTestDB(t *testing.T) *sql.DB {
    t.Helper() // 標記為輔助函式

    db, err := sql.Open("sqlite3", ":memory:")
    if err != nil {
        t.Fatalf("failed to open database: %v", err)
    }

    // 測試結束時清理
    t.Cleanup(func() {
        db.Close()
    })

    // 執行 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) {
    // 建立臨時目錄 - 自動清理
    tmpDir := t.TempDir()

    // 建立測試檔案
    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)
    }

    // 執行測試
    result, err := ProcessFile(testFile)
    if err != nil {
        t.Fatalf("ProcessFile failed: %v", err)
    }

    // 斷言...
    _ = result
}

Golden 檔案

使用儲存在 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 {
                // 更新 golden 檔案: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)
            }
        })
    }
}

使用介面 Mock

基於介面的 Mock

// 定義依賴的介面
type UserRepository interface {
    GetUser(id string) (*User, error)
    SaveUser(user *User) error
}

// 生產實作
type PostgresUserRepository struct {
    db *sql.DB
}

func (r *PostgresUserRepository) GetUser(id string) (*User, error) {
    // 實際資料庫查詢
}

// 測試用 Mock 實作
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)
}

// 使用 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() // 不計算設置時間

    for i := 0; i < b.N; i++ {
        Process(data)
    }
}

// 執行:go test -bench=BenchmarkProcess -benchmem
// 輸出: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++ {
                // 複製以避免排序已排序的資料
                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) {
    // 新增種子語料庫
    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 {
            // 隨機輸入預期會有無效 JSON
            return
        }

        // 如果解析成功,重新編碼應該可行
        _, err = json.Marshal(result)
        if err != nil {
            t.Errorf("Marshal failed after successful Unmarshal: %v", err)
        }
    })
}

// 執行: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)

        // 屬性:Compare(a, a) 應該總是等於 0
        if a == b && result != 0 {
            t.Errorf("Compare(%q, %q) = %d; want 0", a, b, result)
        }

        // 屬性:Compare(a, b) 和 Compare(b, a) 應該有相反符號
        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)
            }
        }
    })
}

測試覆蓋率

執行覆蓋率

# 基本覆蓋率
go test -cover ./...

# 產生覆蓋率 profile
go test -coverprofile=coverage.out ./...

# 在瀏覽器查看覆蓋率
go tool cover -html=coverage.out

# 按函式查看覆蓋率
go tool cover -func=coverage.out

# 含競態偵測的覆蓋率
go test -race -coverprofile=coverage.out ./...

覆蓋率目標

程式碼類型目標
關鍵業務邏輯100%
公開 API90%+
一般程式碼80%+
產生的程式碼排除

HTTP Handler 測試

func TestHealthHandler(t *testing.T) {
    // 建立請求
    req := httptest.NewRequest(http.MethodGet, "/health", nil)
    w := httptest.NewRecorder()

    // 呼叫 handler
    HealthHandler(w, req)

    // 檢查回應
    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)
            }
        })
    }
}

測試指令

# 執行所有測試
go test ./...

# 執行詳細輸出的測試
go test -v ./...

# 執行特定測試
go test -run TestAdd ./...

# 執行匹配模式的測試
go test -run "TestUser/Create" ./...

# 執行帶競態偵測器的測試
go test -race ./...

# 執行帶覆蓋率的測試
go test -cover -coverprofile=coverage.out ./...

# 只執行短測試
go test -short ./...

# 執行帶逾時的測試
go test -timeout 30s ./...

# 執行基準測試
go test -bench=. -benchmem ./...

# 執行模糊測試
go test -fuzz=FuzzParse -fuzztime=30s ./...

# 計算測試執行次數(用於偵測不穩定測試)
go test -count=10 ./...

最佳實務

應該做的:

  • 先寫測試(TDD)
  • 使用表格驅動測試以獲得完整覆蓋
  • 測試行為,而非實作
  • 在輔助函式中使用 t.Helper()
  • 對獨立測試使用 t.Parallel()
  • t.Cleanup() 清理資源
  • 使用描述情境的有意義測試名稱

不應該做的:

  • 不要直接測試私有函式(透過公開 API 測試)
  • 不要在測試中使用 time.Sleep()(使用 channels 或條件)
  • 不要忽略不穩定測試(修復或移除它們)
  • 不要 mock 所有東西(可能時偏好整合測試)
  • 不要跳過錯誤路徑測試

CI/CD 整合

# GitHub Actions 範例
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}'

記住:測試是文件。它們展示你的程式碼應該如何使用。清楚地撰寫並保持更新。

Source repo
affaan-m/ECC
Skill path
docs/zh-TW/skills/golang-testing/SKILL.md
Commit SHA
4e973d3eaf92
Repository license
MIT
Data collected