golang-testing by samber/cc-skills-golang
npx skills add https://github.com/samber/cc-skills-golang --skill golang-testing角色: 你是一名将测试视为可执行规范的 Go 工程师。你编写测试是为了约束行为,而不是为了达到覆盖率目标。
思考模式: 使用 ultrathink 进行测试策略设计和失败分析。浅层推理会遗漏边界情况,并产生脆弱的测试,这些测试今天通过,明天就可能失败。
模式:
gotests 搭建表格驱动测试的框架,然后用边界情况和错误路径来丰富测试。t.Parallel()、与实现细节耦合)。最多启动 3 个并行子代理,按关注点划分:(1) 单元测试质量和覆盖漏洞,(2) 集成测试隔离和构建标签,(3) goroutine 泄漏和竞态条件。社区默认值。 明确取代
samber/cc-skills-golang@golang-testing技能的 company skill 具有优先权。
本技能指导为 Go 应用程序创建可用于生产环境的测试。遵循这些原则来编写可维护、快速且可靠的测试。
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
t.Run 的 name 字段//go:build integration)以与单元测试分离t.Parallel()TestMain 中使用 goleak.VerifyTestMain 来检测 goroutine 泄漏// 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() { ... } // 示例
表格驱动测试是 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)
}
})
}
}
单元测试应该快速(< 1ms)、隔离(无外部依赖)且具有确定性。
使用 httptest 进行处理器测试,并采用表格驱动模式。有关请求/响应体、查询参数、头部和状态码断言的示例,请参阅 HTTP 测试。
使用 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)
// ... test code ...
}
实验性:
testing/synctest尚未纳入 Go 的兼容性保证。其 API 可能在未来的版本中更改。对于稳定的替代方案,请使用clockwork(参见 模拟)。
testing/synctest(Go 1.24+)为并发代码测试提供确定性时间。时间仅在所有 goroutine 都阻塞时才前进,使得顺序可预测。
何时使用 synctest 而非真实时间:
测试具有基于时间操作(time.Sleep、time.After、time.Ticker)的并发代码
当需要可复现的竞态条件时
当测试因时序问题而不稳定时
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 在合成时间达到持续时间时触发对于可能挂起的测试,使用一个会因调用者位置而 panic 的超时辅助函数。参见 辅助函数。
→ 有关高级基准测试:b.Loop()(Go 1.24+)、benchstat、基于基准测试的性能分析以及 CI 回归检测,请参阅 samber/cc-skills-golang@golang-benchmark 技能。
编写基准测试以测量性能并检测回归:
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()
}
})
}
具有不同输入大小的基准测试:
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)
}
})
}
}
使用 t.Parallel() 来并发运行测试:
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)
})
}
}
使用模糊测试来发现边界情况和错误:
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("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 -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 模式和集成测试套件,请参阅 集成测试。
模拟接口,而非具体类型。在消费处定义接口,然后创建模拟实现。
关于模拟模式、测试固定装置和时间模拟,请参阅 模拟。
许多测试最佳实践可以通过 linter 自动强制执行:thelper、paralleltest、testifylint。有关配置和用法,请参阅 samber/cc-skills-golang@golang-linter 技能。
samber/cc-skills-golang@golang-stretchr-testify 技能samber/cc-skills-golang@golang-database 技能(testing.md)samber/cc-skills-golang@golang-concurrency 技能samber/cc-skills-golang@golang-continuous-integration 技能samber/cc-skills-golang@golang-linter 技能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 ./... # 集成测试
每周安装次数
103
代码库
GitHub Stars
184
首次出现
3 天前
安全审计
安装于
opencode83
codex82
gemini-cli82
cursor82
kimi-cli81
github-copilot81
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:
gotests to scaffold table-driven tests, then enrich with edge cases and error paths.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.Community default. A company skill that explicitly supersedes
samber/cc-skills-golang@golang-testingskill takes precedence.
This skill guides the creation of production-ready tests for Go applications. Follow these principles to write maintainable, fast, and reliable tests.
name field passed to t.Run//go:build integration) to separate from unit testst.Parallel() when possiblegoleak.VerifyTestMain in TestMain to detect goroutine leaks// 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
func TestAdd(t *testing.T) { ... } // function test
func TestMyStruct_MyMethod(t *testing.T) { ... } // method test
func BenchmarkAdd(b *testing.B) { ... } // benchmark
func ExampleAdd() { ... } // example
Table-driven tests are the idiomatic Go way to test multiple scenarios. Always name each test case.
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 should be fast (< 1ms), isolated (no external dependencies), and deterministic.
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.
Use go.uber.org/goleak to detect leaking goroutines, especially for concurrent code:
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):
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m,
goleak.IgnoreCurrent(),
)
}
Or per-test:
func TestWorkerPool(t *testing.T) {
defer goleak.VerifyNone(t)
// ... test code ...
}
Experimental:
testing/synctestis not yet covered by Go's compatibility guarantee. Its API may change in future releases. For stable alternatives, useclockwork(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
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 blockstime.After fires when synthetic time reaches the durationFor tests that may hang, use a timeout helper that panics with caller location. See Helpers.
→ 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:
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:
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)
}
})
}
}
Use t.Parallel() to run tests concurrently:
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)
})
}
}
Use fuzzing to find edge cases and bugs:
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 are executable documentation verified by go test:
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
}
# Generate coverage file
go test -coverprofile=coverage.out ./...
# View coverage in HTML
go tool cover -html=coverage.out
# Coverage by function
go tool cover -func=coverage.out
# Total coverage percentage
go tool cover -func=coverage.out | grep total
Use build tags to separate integration tests from unit tests:
//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:
go test -tags=integration ./...
For Docker Compose fixtures, SQL schemas, and integration test suites, see Integration Testing.
Mock interfaces, not concrete types. Define interfaces where consumed, then create mock implementations.
For mock patterns, test fixtures, and time mocking, see Mocking.
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.
samber/cc-skills-golang@golang-stretchr-testify skill for detailed testify API (assert, require, mock, suite)samber/cc-skills-golang@golang-database skill (testing.md) for database integration test patternssamber/cc-skills-golang@golang-concurrency skill for goroutine leak detection with goleaksamber/cc-skills-golang@golang-continuous-integration skill for CI test configuration and GitHub Actions workflowssamber/cc-skills-golang@golang-linter skill for testifylint and paralleltest configurationgo 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
Weekly Installs
103
Repository
GitHub Stars
184
First Seen
3 days ago
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode83
codex82
gemini-cli82
cursor82
kimi-cli81
github-copilot81
后端测试指南:API端点、业务逻辑与数据库测试最佳实践
11,800 周安装