golang-testing

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese
Persona: You are a Go engineer who treats tests as executable specifications. You write tests to constrain behavior, not to hit coverage targets.
Thinking mode: Use
ultrathink
for test strategy design and failure analysis. Shallow reasoning misses edge cases and produces brittle tests that pass today but break tomorrow.
Modes:
  • Write mode — generating new tests for existing or new code. Work sequentially through the code under test; use
    gotests
    to scaffold table-driven tests, then enrich with edge cases and error paths.
  • Review mode — reviewing a PR's test changes. Focus on the diff: check coverage of new behaviour, assertion quality, table-driven structure, and absence of flakiness patterns. Sequential.
  • Audit mode — auditing an existing test suite for gaps, flakiness, or bad patterns (order-dependent tests, missing
    t.Parallel()
    , implementation-detail coupling). Launch up to 3 parallel sub-agents split by concern: (1) unit test quality and coverage gaps, (2) integration test isolation and build tags, (3) goroutine leaks and race conditions.
  • Debug mode — a test is failing or flaky. Work sequentially: reproduce reliably, isolate the failing assertion, trace the root cause in production code or test setup.
Community default. A company skill that explicitly supersedes
samber/cc-skills-golang@golang-testing
skill takes precedence.
角色定位:你是一名将测试视为可执行规范的Go工程师。编写测试是为了约束代码行为,而非单纯达到覆盖率指标。
思考模式:使用
ultrathink
进行测试策略设计和故障分析。浅层推理会遗漏边缘情况,导致测试在当前能通过,但后续容易失效。
模式说明
  • 编写模式——为现有或新增代码生成测试用例。按顺序处理被测试代码;使用
    gotests
    快速搭建表格驱动测试的框架,然后补充边缘情况和错误路径的测试。
  • 评审模式——评审PR中的测试变更。重点关注差异:检查新功能的覆盖情况、断言质量、表格驱动结构,以及是否存在导致测试不稳定的模式。按顺序执行评审。
  • 审计模式——审计现有测试套件的漏洞、不稳定性或不良模式(如依赖执行顺序的测试、未使用
    t.Parallel()
    、与实现细节耦合等)。最多可启动3个并行子代理,按关注点拆分:(1) 单元测试质量与覆盖率缺口,(2) 集成测试隔离与构建标签,(3) Goroutine泄漏与竞态条件。
  • 调试模式——测试失败或不稳定时。按顺序操作:先稳定复现问题,定位失败的断言,追踪生产代码或测试设置中的根本原因。
社区默认规则:如果公司内部有明确替代
samber/cc-skills-golang@golang-testing
的技能,则以该技能为准。

Go Testing Best Practices

Go测试最佳实践

This skill guides the creation of production-ready tests for Go applications. Follow these principles to write maintainable, fast, and reliable tests.
本文指导你为Go应用编写可用于生产环境的测试。遵循以下原则,编写可维护、快速且可靠的测试。

Best Practices Summary

最佳实践摘要

  1. Table-driven tests MUST use named subtests -- every test case needs a
    name
    field passed to
    t.Run
  2. Integration tests MUST use build tags (
    //go:build integration
    ) to separate from unit tests
  3. Tests MUST NOT depend on execution order -- each test MUST be independently runnable
  4. Independent tests SHOULD use
    t.Parallel()
    when possible
  5. NEVER test implementation details -- test observable behavior and public API contracts
  6. Packages with goroutines SHOULD use
    goleak.VerifyTestMain
    in
    TestMain
    to detect goroutine leaks
  7. Use testify as helpers, not a replacement for standard library
  8. Mock interfaces, not concrete types
  9. Keep unit tests fast (< 1ms), use build tags for integration tests
  10. Run tests with race detection in CI
  11. Include examples as executable documentation
  1. 表格驱动测试必须使用命名子测试——每个测试用例都需要一个
    name
    字段传入
    t.Run
  2. 集成测试必须使用构建标签(
    //go:build integration
    )与单元测试分离
  3. 测试不得依赖执行顺序——每个测试都必须可独立运行
  4. 独立测试应尽可能使用
    t.Parallel()
  5. 绝对不要测试实现细节——仅测试可观察的行为和公开API契约
  6. 包含Goroutine的包应在
    TestMain
    中使用
    goleak.VerifyTestMain
    来检测Goroutine泄漏
  7. 将testify作为辅助工具,而非标准库的替代品
  8. Mock接口,而非具体类型
  9. 保持单元测试快速(<1毫秒),使用构建标签区分集成测试
  10. 在CI中启用竞态检测来运行测试
  11. 将示例代码作为可执行文档

Test Structure and Organization

测试结构与组织

File Conventions

文件命名规范

go
// package_test.go - tests in same package (white-box, access unexported)
package mypackage

// mypackage_test.go - tests in test package (black-box, public API only)
package mypackage_test
go
// package_test.go - 与源码同包的测试(白盒测试,可访问未导出成员)
package mypackage

// mypackage_test.go - 独立测试包的测试(黑盒测试,仅测试公开API)
package mypackage_test

Naming Conventions

命名约定

go
func TestAdd(t *testing.T) { ... }              // function test
func TestMyStruct_MyMethod(t *testing.T) { ... } // method test
func BenchmarkAdd(b *testing.B) { ... }          // benchmark
func ExampleAdd() { ... }                        // example
go
func TestAdd(t *testing.T) { ... }              // 函数测试
func TestMyStruct_MyMethod(t *testing.T) { ... } // 方法测试
func BenchmarkAdd(b *testing.B) { ... }          // 基准测试
func ExampleAdd() { ... }                        // 示例代码

Table-Driven Tests

表格驱动测试

Table-driven tests are the idiomatic Go way to test multiple scenarios. Always name each test case.
go
func TestCalculatePrice(t *testing.T) {
    tests := []struct {
        name     string
        quantity int
        unitPrice float64
        expected  float64
    }{
        {
            name:      "single item",
            quantity:  1,
            unitPrice: 10.0,
            expected:  10.0,
        },
        {
            name:      "bulk discount - 100 items",
            quantity:  100,
            unitPrice: 10.0,
            expected:  900.0, // 10% discount
        },
        {
            name:      "zero quantity",
            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)
            }
        })
    }
}
表格驱动测试是Go语言中测试多场景的惯用方式,务必为每个测试用例命名。
go
func TestCalculatePrice(t *testing.T) {
    tests := []struct {
        name     string
        quantity int
        unitPrice float64
        expected  float64
    }{
        {
            name:      "single item",
            quantity:  1,
            unitPrice: 10.0,
            expected:  10.0,
        },
        {
            name:      "bulk discount - 100 items",
            quantity:  100,
            unitPrice: 10.0,
            expected:  900.0, // 10% discount
        },
        {
            name:      "zero quantity",
            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)
            }
        })
    }
}

Unit Tests

单元测试

Unit tests should be fast (< 1ms), isolated (no external dependencies), and deterministic.
单元测试应快速(<1毫秒)、隔离(无外部依赖)且结果可预测。

Testing HTTP Handlers

HTTP处理器测试

Use
httptest
for handler tests with table-driven patterns. See HTTP Testing for examples with request/response bodies, query parameters, headers, and status code assertions.
使用
httptest
结合表格驱动模式测试处理器。可参考HTTP测试中的示例,包含请求/响应体、查询参数、请求头和状态码断言等内容。

Goroutine Leak Detection with goleak

使用goleak检测Goroutine泄漏

Use
go.uber.org/goleak
to detect leaking goroutines, especially for concurrent code:
go
import (
    "testing"
    "go.uber.org/goleak"
)

func TestMain(m *testing.M) {
    goleak.VerifyTestMain(m)
}
To exclude specific goroutine stacks (for known leaks or library goroutines):
go
func TestMain(m *testing.M) {
    goleak.VerifyTestMain(m,
        goleak.IgnoreCurrent(),
    )
}
Or per-test:
go
func TestWorkerPool(t *testing.T) {
    defer goleak.VerifyNone(t)
    // ... test code ...
}
使用
go.uber.org/goleak
检测泄漏的Goroutine,尤其是在并发代码中:
go
import (
    "testing"
    "go.uber.org/goleak"
)

func TestMain(m *testing.M) {
    goleak.VerifyTestMain(m)
}
如需排除特定Goroutine栈(针对已知泄漏或库级Goroutine):
go
func TestMain(m *testing.M) {
    goleak.VerifyTestMain(m,
        goleak.IgnoreCurrent(),
    )
}
或针对单个测试:
go
func TestWorkerPool(t *testing.T) {
    defer goleak.VerifyNone(t)
    // ... 测试代码 ...
}

testing/synctest for Deterministic Goroutine Testing

使用testing/synctest进行确定性Goroutine测试

Experimental:
testing/synctest
is not yet covered by Go's compatibility guarantee. Its API may change in future releases. For stable alternatives, use
clockwork
(see Mocking).
testing/synctest
(Go 1.24+) provides deterministic time for concurrent code testing. Time advances only when all goroutines are blocked, making ordering predictable.
When to use
synctest
instead of real time:
  • Testing concurrent code with time-based operations (time.Sleep, time.After, time.Ticker)
  • When race conditions need to be reproducible
  • When tests are flaky due to timing issues
go
import (
    "testing"
    "time"
    "testing/synctest"
    "github.com/stretchr/testify/assert"
)

func TestChannelTimeout(t *testing.T) {
    synctest.Run(func(t *testing.T) {
        is := assert.New(t)

        ch := make(chan int, 1)
        go func() {
            time.Sleep(50 * time.Millisecond)
            ch <- 42
        }()

        select {
        case v := <-ch:
            is.Equal(42, v)
        case <-time.After(100 * time.Millisecond):
            t.Fatal("timeout occurred")
        }
    })
}
Key differences in
synctest
:
  • time.Sleep
    advances synthetic time instantly when the goroutine blocks
  • time.After
    fires when synthetic time reaches the duration
  • All goroutines run to blocking points before time advances
  • Test execution is deterministic and repeatable
实验性功能
testing/synctest
尚未纳入Go的兼容性保障范围,其API可能在未来版本中变更。如需稳定替代方案,请使用
clockwork
(参考Mocking)。
testing/synctest
(Go 1.24+)为并发代码测试提供确定性时间。仅当所有Goroutine都处于阻塞状态时,时间才会推进,使得执行顺序可预测。
适合使用
synctest
而非真实时间的场景:
  • 测试包含基于时间操作的并发代码(time.Sleep、time.After、time.Ticker)
  • 需要可复现的竞态条件
  • 测试因时序问题导致不稳定
go
import (
    "testing"
    "time"
    "testing/synctest"
    "github.com/stretchr/testify/assert"
)

func TestChannelTimeout(t *testing.T) {
    synctest.Run(func(t *testing.T) {
        is := assert.New(t)

        ch := make(chan int, 1)
        go func() {
            time.Sleep(50 * time.Millisecond)
            ch <- 42
        }()

        select {
        case v := <-ch:
            is.Equal(42, v)
        case <-time.After(100 * time.Millisecond):
            t.Fatal("timeout occurred")
        }
    })
}
synctest
的核心差异:
  • time.Sleep
    会在Goroutine阻塞时立即推进虚拟时间
  • time.After
    会在虚拟时间达到指定时长时触发
  • 所有Goroutine都进入阻塞状态后,时间才会推进
  • 测试执行具有确定性且可重复

Test Timeouts

测试超时

For tests that may hang, use a timeout helper that panics with caller location. See Helpers.
对于可能挂起的测试,使用包含调用者位置的超时辅助工具。参考Helpers

Benchmarks

基准测试

→ See
samber/cc-skills-golang@golang-benchmark
skill for advanced benchmarking:
b.Loop()
(Go 1.24+),
benchstat
, profiling from benchmarks, and CI regression detection.
Write benchmarks to measure performance and detect regressions:
go
func BenchmarkStringConcatenation(b *testing.B) {
    b.Run("plus-operator", func(b *testing.B) {
        for i := 0; i < b.N; i++ {
            result := "a" + "b" + "c"
            _ = result
        }
    })

    b.Run("strings.Builder", func(b *testing.B) {
        for i := 0; i < b.N; i++ {
            var builder strings.Builder
            builder.WriteString("a")
            builder.WriteString("b")
            builder.WriteString("c")
            _ = builder.String()
        }
    })
}
Benchmarks with different input sizes:
go
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 i := 0; i < b.N; i++ {
                Fibonacci(size)
            }
        })
    }
}
→ 如需高级基准测试内容,请参考
samber/cc-skills-golang@golang-benchmark
技能:
b.Loop()
(Go 1.24+)、
benchstat
、基于基准测试的性能分析以及CI中的回归检测。
编写基准测试以衡量性能并检测回归:
go
func BenchmarkStringConcatenation(b *testing.B) {
    b.Run("plus-operator", func(b *testing.B) {
        for i := 0; i < b.N; i++ {
            result := "a" + "b" + "c"
            _ = result
        }
    })

    b.Run("strings.Builder", func(b *testing.B) {
        for i := 0; i < b.N; i++ {
            var builder strings.Builder
            builder.WriteString("a")
            builder.WriteString("b")
            builder.WriteString("c")
            _ = builder.String()
        }
    })
}
不同输入规模的基准测试:
go
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 i := 0; i < b.N; i++ {
                Fibonacci(size)
            }
        })
    }
}

Parallel Tests

并行测试

Use
t.Parallel()
to run tests concurrently:
go
func TestParallelOperations(t *testing.T) {
    tests := []struct {
        name string
        data []byte
    }{
        {"small data", make([]byte, 1024)},
        {"medium data", 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)
        })
    }
}
使用
t.Parallel()
并发运行测试:
go
func TestParallelOperations(t *testing.T) {
    tests := []struct {
        name string
        data []byte
    }{
        {"small data", make([]byte, 1024)},
        {"medium data", 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)
        })
    }
}

Fuzzing

模糊测试

Use fuzzing to find edge cases and bugs:
go
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)
        }
    })
}
使用模糊测试发现边缘情况和Bug:
go
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)
        }
    })
}

Examples as Documentation

示例代码作为文档

Examples are executable documentation verified by
go test
:
go
func ExampleCalculatePrice() {
    price := CalculatePrice(100, 10.0)
    fmt.Printf("Price: %.2f\n", price)
    // Output: Price: 900.00
}

func ExampleCalculatePrice_singleItem() {
    price := CalculatePrice(1, 25.50)
    fmt.Printf("Price: %.2f\n", price)
    // Output: Price: 25.50
}
示例代码是可执行的文档,会被
go test
验证:
go
func ExampleCalculatePrice() {
    price := CalculatePrice(100, 10.0)
    fmt.Printf("Price: %.2f\n", price)
    // Output: Price: 900.00
}

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

Code Coverage

代码覆盖率

bash
undefined
bash
undefined

Generate coverage file

生成覆盖率文件

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

View coverage in HTML

以HTML形式查看覆盖率

go tool cover -html=coverage.out
go tool cover -html=coverage.out

Coverage by function

按函数查看覆盖率

go tool cover -func=coverage.out
go tool cover -func=coverage.out

Total coverage percentage

查看总覆盖率百分比

go tool cover -func=coverage.out | grep total
undefined
go tool cover -func=coverage.out | grep total
undefined

Integration Tests

集成测试

Use build tags to separate integration tests from unit tests:
go
//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()

    // Test real database operations
}
Run integration tests separately:
bash
go test -tags=integration ./...
For Docker Compose fixtures, SQL schemas, and integration test suites, see Integration Testing.
使用构建标签将集成测试与单元测试分离:
go
//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()

    // 测试真实数据库操作
}
单独运行集成测试:
bash
go test -tags=integration ./...
如需Docker Compose测试夹具、SQL Schema和集成测试套件相关内容,请参考集成测试

Mocking

Mocking

Mock interfaces, not concrete types. Define interfaces where consumed, then create mock implementations.
For mock patterns, test fixtures, and time mocking, see Mocking.
Mock接口,而非具体类型。在消费端定义接口,然后创建Mock实现。
如需Mock模式、测试夹具和时间Mock相关内容,请参考Mocking

Enforce with Linters

使用Linter强制执行规范

Many test best practices are enforced automatically by linters:
thelper
,
paralleltest
,
testifylint
. See the
samber/cc-skills-golang@golang-linter
skill for configuration and usage.
许多测试最佳实践可通过Linter自动强制执行:
thelper
paralleltest
testifylint
。配置和使用方法请参考
samber/cc-skills-golang@golang-linter
技能。

Cross-References

交叉引用

  • -> See
    samber/cc-skills-golang@golang-stretchr-testify
    skill for detailed testify API (assert, require, mock, suite)
  • -> See
    samber/cc-skills-golang@golang-database
    skill (testing.md) for database integration test patterns
  • -> See
    samber/cc-skills-golang@golang-concurrency
    skill for goroutine leak detection with goleak
  • -> See
    samber/cc-skills-golang@golang-continuous-integration
    skill for CI test configuration and GitHub Actions workflows
  • -> See
    samber/cc-skills-golang@golang-linter
    skill for testifylint and paralleltest configuration
  • → 如需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-linter
    技能

Quick Reference

快速参考

bash
go test ./...                          # all tests
go test -run TestName ./...            # specific test by exact name
go test -run TestName/subtest ./...    # subtests within a test
go test -run 'Test(Add|Sub)' ./...     # multiple tests (regexp OR)
go test -run 'Test[A-Z]' ./...         # tests starting with capital letter
go test -run 'TestUser.*' ./...        # tests matching prefix
go test -run '.*Validation.*' ./...    # tests containing substring
go test -run TestName/. ./...          # all subtests of TestName
go test -run '/(unit|integration)' ./... # filter by subtest name
go test -race ./...                    # race detection
go test -cover ./...                   # coverage summary
go test -bench=. -benchmem ./...       # benchmarks
go test -fuzz=FuzzName ./...           # fuzzing
go test -tags=integration ./...        # integration tests
bash
go test ./...                          # 运行所有测试
go test -run TestName ./...            # 运行指定名称的测试
go test -run TestName/subtest ./...    # 运行指定测试下的子测试
go test -run 'Test(Add|Sub)' ./...     # 运行多个测试(正则或匹配)
go test -run 'Test[A-Z]' ./...         # 运行以大写字母开头的测试
go test -run 'TestUser.*' ./...        # 运行匹配前缀的测试
go test -run '.*Validation.*' ./...    # 运行包含指定子串的测试
go test -run TestName/. ./...          # 运行指定测试的所有子测试
go test -run '/(unit|integration)' ./... # 按子测试名称过滤
go test -race ./...                    # 启用竞态检测运行测试
go test -cover ./...                   # 查看覆盖率摘要
go test -bench=. -benchmem ./...       # 运行基准测试
go test -fuzz=FuzzName ./...           # 运行模糊测试
go test -tags=integration ./...        # 运行集成测试