
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`。
生產就緒的 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 應用程式建立生產就緒的測試。遵循這些原則以撰寫可維護、快速且可靠的測試。
最佳實務摘要
- 表格驅動測試必須使用具名子測試 — 每個測試案例都需要一個傳遞給
t.Run的name欄位 - 整合測試必須使用建置標籤(
//go:build integration)與單元測試分離 - 測試不得依賴執行順序 — 每個測試必須可獨立執行
- 獨立的測試應盡可能使用
t.Parallel() - 絕對不要測試實作細節 — 測試可觀察的行為與公開 API 合約
- 包含 goroutine 的套件應在
TestMain中使用goleak.VerifyTestMain來偵測 goroutine 洩漏 - 將 testify 作為輔助工具,而非取代標準函式庫
- Mock 介面,而非具體型別
- 保持單元測試快速(< 1ms),使用建置標籤區分整合測試
- 在 CI 中使用競爭檢測執行測試
- 包含範例作為可執行的文件
測試結構與組織
檔案慣例
// 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 自動強制執行:thelper、paralleltest、testifylint。請參閱 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 ./... # 整合測試





