golang-testing

golang-testing

熱門

生產就緒的 Golang 測試 — 表格驅動測試、testify 測試套件與 mock、平行測試、模糊測試、測試固定裝置、使用 goleak 偵測 goroutine 洩漏、快照測試、程式碼覆蓋率、整合測試、慣例的測試命名。在撰寫或審查 Go 測試、選擇測試方法、設定 Go 測試 CI、或除錯不穩定/慢速測試時使用。關於 testify 專用 API 請參閱 `samber/cc-skills-golang@golang-stretchr-testify`;關於測量方法論請參閱 `samber/cc-skills-golang@golang-benchmark`。

2261星標
150分支
更新於 2026/6/6
SKILL.md
readonlyread-only
name
golang-testing
description

生產就緒的 Golang 測試 — 表格驅動測試、testify 測試套件與 mock、平行測試、模糊測試、測試固定裝置、使用 goleak 偵測 goroutine 洩漏、快照測試、程式碼覆蓋率、整合測試、慣例的測試命名。在撰寫或審查 Go 測試、選擇測試方法、設定 Go 測試 CI、或除錯不穩定/慢速測試時使用。關於 testify 專用 API 請參閱 `samber/cc-skills-golang@golang-stretchr-testify`;關於測量方法論請參閱 `samber/cc-skills-golang@golang-benchmark`。

角色設定: 你是一位將測試視為可執行規格的 Go 工程師。你撰寫測試是為了約束行為,而不是為了達到覆蓋率目標。

思考模式: 在測試策略設計與失敗分析時使用 ultrathink。淺層推理會遺漏邊界情況,產生今天通過但明天就壞掉的脆弱測試。

模式:

  • 撰寫模式 — 為現有或新程式碼產生新測試。依序處理受測程式碼;使用 gotests 建立表格驅動測試的骨架,然後補充邊界情況與錯誤路徑。
  • 審查模式 — 審查 PR 中的測試變更。專注於差異:檢查新行為的覆蓋率、斷言品質、表格驅動結構、以及是否有不穩定的模式。依序進行。
  • 稽核模式 — 稽核現有測試套件中的缺口、不穩定或不良模式(順序相依測試、缺少 t.Parallel()、與實作細節耦合)。最多啟動 3 個平行子代理,按關注點拆分:(1) 單元測試品質與覆蓋率缺口,(2) 整合測試隔離與建置標籤,(3) goroutine 洩漏與競爭條件。
  • 除錯模式 — 測試失敗或不穩定。依序進行:可靠地重現、隔離失敗的斷言、追蹤生產程式碼或測試設定中的根本原因。

社群預設值。 明確取代 samber/cc-skills-golang@golang-testing 技能的團隊技能具有優先權。

相依性:

  • gotests: go install github.com/cweill/gotests/gotests@latest

Go 測試最佳實務

此技能引導為 Go 應用程式建立生產就緒的測試。遵循這些原則以撰寫可維護、快速且可靠的測試。

最佳實務摘要

  1. 表格驅動測試必須使用具名子測試 — 每個測試案例都需要一個傳遞給 t.Runname 欄位
  2. 整合測試必須使用建置標籤(//go:build integration)與單元測試分離
  3. 測試不得依賴執行順序 — 每個測試必須可獨立執行
  4. 獨立的測試應盡可能使用 t.Parallel()
  5. 絕對不要測試實作細節 — 測試可觀察的行為與公開 API 合約
  6. 包含 goroutine 的套件應在 TestMain 中使用 goleak.VerifyTestMain 來偵測 goroutine 洩漏
  7. 將 testify 作為輔助工具,而非取代標準函式庫
  8. Mock 介面,而非具體型別
  9. 保持單元測試快速(< 1ms),使用建置標籤區分整合測試
  10. 在 CI 中使用競爭檢測執行測試
  11. 包含範例作為可執行的文件

測試結構與組織

檔案慣例

// package_test.go - 相同套件內的測試(白箱,可存取未匯出的內容)
package mypackage

// mypackage_test.go - 測試套件中的測試(黑箱,僅公開 API)
package mypackage_test

命名慣例

func TestAdd(t *testing.T) { ... }               // 函式測試
func TestMyStruct_MyMethod(t *testing.T) { ... } // 方法測試
func BenchmarkAdd(b *testing.B) { ... }          // 基準測試
func ExampleAdd() { ... }                        // 範例
func FuzzAdd(f *testing.F) { ... }               // 模糊測試

表格驅動測試

表格驅動測試是測試多種情境的慣用 Go 方式。務必為每個測試案例命名。

func TestCalculatePrice(t *testing.T) {
    tests := []struct {
        name     string
        quantity int
        unitPrice float64
        expected  float64
    }{
        {
            name:      "單一商品",
            quantity:  1,
            unitPrice: 10.0,
            expected:  10.0,
        },
        {
            name:      "大量折扣 - 100 件",
            quantity:  100,
            unitPrice: 10.0,
            expected:  900.0, // 10% 折扣
        },
        {
            name:      "數量為零",
            quantity:  0,
            unitPrice: 10.0,
            expected:  0.0,
        },
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got := CalculatePrice(tt.quantity, tt.unitPrice)
            if got != tt.expected {
                t.Errorf("CalculatePrice(%d, %.2f) = %.2f, want %.2f",
                    tt.quantity, tt.unitPrice, got, tt.expected)
            }
        })
    }
}

單元測試

單元測試應快速(< 1ms)、隔離(無外部相依性)且確定性。

測試 HTTP Handler

使用 httptest 搭配表格驅動模式進行 handler 測試。關於請求/回應主體、查詢參數、標頭與狀態碼斷言的範例,請參閱 HTTP Testing

使用 goleak 偵測 Goroutine 洩漏

使用 go.uber.org/goleak 偵測洩漏的 goroutine,特別適用於並行程式碼:

import (
    "testing"
    "go.uber.org/goleak"
)

func TestMain(m *testing.M) {
    goleak.VerifyTestMain(m)
}

若要排除特定的 goroutine 堆疊(針對已知洩漏或函式庫 goroutine):

func TestMain(m *testing.M) {
    goleak.VerifyTestMain(m,
        goleak.IgnoreCurrent(),
    )
}

或逐個測試:

func TestWorkerPool(t *testing.T) {
    defer goleak.VerifyNone(t)
    // ... 測試程式碼 ...
}

使用 testing/synctest 進行確定性 Goroutine 測試

testing/synctest(Go 1.25+)為 goroutine、計時器、截止時間與 context 取消提供確定性測試。時間僅在所有 goroutine 都阻塞時才前進,使順序可預測。

何時使用 synctest 而非真實時間:

  • 測試包含基於時間操作的並行程式碼(time.Sleep、time.After、time.Ticker)
  • 當競爭條件需要可重現時
  • 當測試因時間問題而不穩定時
import (
    "context"
    "testing"
    "testing/synctest"
    "time"
)

func TestContextTimeout(t *testing.T) {
    synctest.Test(t, func(t *testing.T) {
        const timeout = 5 * time.Second

        ctx, cancel := context.WithTimeout(t.Context(), timeout)
        defer cancel()

        time.Sleep(timeout - time.Nanosecond)
        synctest.Wait()
        if err := ctx.Err(); err != nil {
            t.Fatalf("逾時前: %v", err)
        }

        time.Sleep(time.Nanosecond)
        synctest.Wait()
        if err := ctx.Err(); err != context.DeadlineExceeded {
            t.Fatalf("逾時後: got %v, want DeadlineExceeded", err)
        }
    })
}

在 Go 1.25+ 與 Go 1.26+ 中使用 synctest.Test。不要在 Go 1.25+ 或 Go 1.26+ 的程式碼中使用舊的 Go 1.24 實驗性 synctest.Run API。如果模組明確以 Go 1.24 為目標並啟用 GOEXPERIMENT=synctest,僅在相容性回退時使用舊 API。

synctest 的主要差異:

  • time.Sleep 在 goroutine 阻塞時立即推進合成時間
  • time.After 在合成時間到達持續時間時觸發
  • 所有 goroutine 執行到阻塞點後時間才前進
  • 測試執行是確定性且可重複的

測試逾時

對於可能掛起的測試,使用會 panic 並附帶呼叫者位置的逾時輔助函式。請參閱 Helpers

基準測試

→ 進階基準測試請參閱 samber/cc-skills-golang@golang-benchmark 技能:b.Loop()(Go 1.24+)、benchstat、從基準測試進行效能分析,以及 CI 回歸檢測。

撰寫基準測試以衡量效能並檢測回歸:

func BenchmarkStringConcatenation(b *testing.B) {
    b.Run("plus-operator", func(b *testing.B) {
        for b.Loop() {
            result := "a" + "b" + "c"
            _ = result
        }
    })

    b.Run("strings.Builder", func(b *testing.B) {
        for b.Loop() {
            var builder strings.Builder
            builder.WriteString("a")
            builder.WriteString("b")
            builder.WriteString("c")
            _ = builder.String()
        }
    })
}

不同輸入大小的基準測試:

func BenchmarkFibonacci(b *testing.B) {
    sizes := []int{10, 20, 30}
    for _, size := range sizes {
        b.Run(fmt.Sprintf("n=%d", size), func(b *testing.B) {
            b.ReportAllocs()
            for b.Loop() {
                Fibonacci(size)
            }
        })
    }
}

對於 Go 1.24+,新的基準測試應使用 b.Loop()。僅在模組目標為 Go <1.24 或刻意保留舊基準測試程式碼時,才使用舊的 b.N 迴圈。

Go 1.26+:測試工件

當測試、基準測試或模糊測試目標需要保留檔案以供檢查時,使用 ArtifactDir() 而非臨時路徑或儲存庫本機輸出。

func TestRenderGoldenArtifact(t *testing.T) {
    dir := t.ArtifactDir()

    out := filepath.Join(dir, "rendered.json")
    if err := os.WriteFile(out, renderedBytes, 0o644); err != nil {
        t.Fatal(err)
    }

    t.Logf("工件已寫入: %s", out)
}

在 Go 1.26+ 中可用於 *testing.T*testing.B*testing.F

平行測試

使用 t.Parallel() 來同時執行測試:

func TestParallelOperations(t *testing.T) {
    tests := []struct {
        name string
        data []byte
    }{
        {"小資料", make([]byte, 1024)},
        {"中資料", make([]byte, 1024*1024)},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            t.Parallel()
            is := assert.New(t)

            result := Process(tt.data)
            is.NotNil(result)
        })
    }
}

模糊測試

使用模糊測試來找出邊界情況與錯誤:

func FuzzReverse(f *testing.F) {
    f.Add("hello")
    f.Add("")
    f.Add("a")

    f.Fuzz(func(t *testing.T, input string) {
        reversed := Reverse(input)
        doubleReversed := Reverse(reversed)
        if input != doubleReversed {
            t.Errorf("Reverse(Reverse(%q)) = %q, want %q", input, doubleReversed, input)
        }
    })
}

範例作為文件

範例是由 go test 驗證的可執行文件:

func ExampleCalculatePrice() {
    price := CalculatePrice(100, 10.0)
    fmt.Printf("價格: %.2f\n", price)
    // Output: 價格: 900.00
}

func ExampleCalculatePrice_singleItem() {
    price := CalculatePrice(1, 25.50)
    fmt.Printf("價格: %.2f\n", price)
    // Output: 價格: 25.50
}

程式碼覆蓋率

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

# 以 HTML 檢視覆蓋率
go tool cover -html=coverage.out

# 依函式檢視覆蓋率
go tool cover -func=coverage.out

# 總覆蓋率百分比
go tool cover -func=coverage.out | grep total

整合測試

使用建置標籤將整合測試與單元測試分離:

//go:build integration

package mypackage

func TestDatabaseIntegration(t *testing.T) {
    db, err := sql.Open("postgres", os.Getenv("DATABASE_URL"))
    if err != nil {
        t.Fatal(err)
    }
    defer db.Close()

    // 測試真實資料庫操作
}

單獨執行整合測試:

go test -tags=integration ./...

關於 Docker Compose 固定裝置、SQL schema 與整合測試套件,請參閱 Integration Testing

Mocking

Mock 介面,而非具體型別。在使用處定義介面,然後建立 mock 實作。

關於 mock 模式、測試固定裝置與時間 mock,請參閱 Mocking

使用 Linter 強制執行

許多測試最佳實務由 linter 自動強制執行:thelperparalleltesttestifylint。請參閱 samber/cc-skills-golang@golang-lint 技能以了解設定與使用方式。

交叉參考

  • -> 詳細的 testify API(assert、require、mock、suite)請參閱 samber/cc-skills-golang@golang-stretchr-testify 技能
  • -> 資料庫整合測試模式請參閱 samber/cc-skills-golang@golang-database 技能(testing.md
  • -> 使用 goleak 偵測 goroutine 洩漏請參閱 samber/cc-skills-golang@golang-concurrency 技能
  • -> CI 測試設定與 GitHub Actions 工作流程請參閱 samber/cc-skills-golang@golang-continuous-integration 技能
  • -> testifylint 與 paralleltest 設定請參閱 samber/cc-skills-golang@golang-lint 技能
  • -> 在 CI 中使用這些指南進行自動化 AI 驅動程式碼審查請參閱 samber/cc-skills-golang@golang-continuous-integration 技能

快速參考

go test ./...                          # 所有測試
go test -run TestName ./...            # 依確切名稱指定測試
go test -run TestName/subtest ./...    # 測試內的子測試
go test -run 'Test(Add|Sub)' ./...     # 多個測試(正規表示式 OR)
go test -run 'Test[A-Z]' ./...         # 以大寫字母開頭的測試
go test -run 'TestUser.*' ./...        # 符合前綴的測試
go test -run '.*Validation.*' ./...    # 包含子字串的測試
go test -run TestName/. ./...          # TestName 的所有子測試
go test -run '/(unit|integration)' ./... # 依子測試名稱過濾
go test -race ./...                    # 競爭檢測
go test -cover ./...                   # 覆蓋率摘要
go test -bench=. -benchmem ./...       # 基準測試
go test -fuzz=FuzzName ./...           # 模糊測試
go test -tags=integration ./...        # 整合測試