golang-testing-strategies by bobmatnyc/claude-mpm-skills
npx skills add https://github.com/bobmatnyc/claude-mpm-skills --skill golang-testing-strategiesGo 提供了一个强大的内置测试框架(testing 包),强调简洁性和开发者的生产力。结合 testify 和 gomock 等社区工具,Go 测试能够以最少的样板代码实现全面的测试覆盖。
主要特性:
在以下情况下激活此技能:
t.Run() 组织相关测试广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
*_test.go 文件与代码放在一起package_test,内部测试使用 package文件命名约定:
file_test.gofile_integration_test.goBenchmark 前缀包结构:
mypackage/
├── user.go
├── user_test.go // 内部测试(同一包)
├── user_external_test.go // 外部测试(包 mypackage_test)
├── integration_test.go // 集成测试
└── testdata/ // 测试夹具(被 go build 忽略)
└── golden.json
用于测试多个输入的惯用 Go 测试模式:
func TestUserValidation(t *testing.T) {
tests := []struct {
name string
input User
wantErr bool
errMsg string
}{
{
name: "valid user",
input: User{Name: "Alice", Age: 30, Email: "alice@example.com"},
wantErr: false,
},
{
name: "empty name",
input: User{Name: "", Age: 30, Email: "alice@example.com"},
wantErr: true,
errMsg: "name is required",
},
{
name: "invalid email",
input: User{Name: "Bob", Age: 25, Email: "invalid"},
wantErr: true,
errMsg: "invalid email format",
},
{
name: "negative age",
input: User{Name: "Charlie", Age: -5, Email: "charlie@example.com"},
wantErr: true,
errMsg: "age must be positive",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := ValidateUser(tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("ValidateUser() error = %v, wantErr %v", err, tt.wantErr)
return
}
if tt.wantErr && err.Error() != tt.errMsg {
t.Errorf("ValidateUser() error message = %v, want %v", err.Error(), tt.errMsg)
}
})
}
}
为独立的测试启用并行执行:
func TestConcurrentOperations(t *testing.T) {
tests := []struct {
name string
fn func() int
want int
}{
{"operation 1", func() int { return compute1() }, 42},
{"operation 2", func() int { return compute2() }, 84},
{"operation 3", func() int { return compute3() }, 126},
}
for _, tt := range tests {
tt := tt // 捕获循环变量
t.Run(tt.name, func(t *testing.T) {
t.Parallel() // 并发运行测试
got := tt.fn()
if got != tt.want {
t.Errorf("got %v, want %v", got, tt.want)
}
})
}
}
go get github.com/stretchr/testify
用可读性强的断言替换冗长的错误检查:
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCalculator(t *testing.T) {
calc := NewCalculator()
// assert: 失败时测试继续
assert.Equal(t, 5, calc.Add(2, 3))
assert.NotNil(t, calc)
assert.True(t, calc.IsReady())
// require: 失败时测试停止(用于关键断言)
result, err := calc.Divide(10, 2)
require.NoError(t, err) // 如果发生错误则停止
assert.Equal(t, 5, result)
}
func TestUserOperations(t *testing.T) {
user := &User{ID: 1, Name: "Alice", Email: "alice@example.com"}
// 对象匹配
assert.Equal(t, 1, user.ID)
assert.Contains(t, user.Email, "@")
assert.Len(t, user.Name, 5)
// 部分匹配
assert.ObjectsAreEqual(user, &User{
ID: 1,
Name: "Alice",
Email: assert.AnythingOfType("string"),
})
}
使用 setup/teardown 组织相关测试:
import (
"testing"
"github.com/stretchr/testify/suite"
)
type UserServiceTestSuite struct {
suite.Suite
db *sql.DB
service *UserService
}
// SetupSuite 在所有测试之前运行一次
func (s *UserServiceTestSuite) SetupSuite() {
s.db = setupTestDatabase()
s.service = NewUserService(s.db)
}
// TearDownSuite 在所有测试之后运行一次
func (s *UserServiceTestSuite) TearDownSuite() {
s.db.Close()
}
// SetupTest 在每个测试之前运行
func (s *UserServiceTestSuite) SetupTest() {
cleanDatabase(s.db)
}
// TearDownTest 在每个测试之后运行
func (s *UserServiceTestSuite) TearDownTest() {
// 如果需要清理
}
// 测试方法必须以 "Test" 开头
func (s *UserServiceTestSuite) TestCreateUser() {
user := &User{Name: "Alice", Email: "alice@example.com"}
err := s.service.Create(user)
s.NoError(err)
s.NotEqual(0, user.ID) // ID 已分配
}
func (s *UserServiceTestSuite) TestGetUser() {
// 设置
user := &User{Name: "Bob", Email: "bob@example.com"}
s.service.Create(user)
// 测试
retrieved, err := s.service.GetByID(user.ID)
s.NoError(err)
s.Equal(user.Name, retrieved.Name)
}
// 运行套件
func TestUserServiceTestSuite(t *testing.T) {
suite.Run(t, new(UserServiceTestSuite))
}
go install github.com/golang/mock/mockgen@latest
// user_repository.go
package repository
//go:generate mockgen -source=user_repository.go -destination=mocks/mock_user_repository.go -package=mocks
type UserRepository interface {
GetByID(id int) (*User, error)
Create(user *User) error
Update(user *User) error
Delete(id int) error
}
生成模拟:
go generate ./...
# 或者手动:
mockgen -source=user_repository.go -destination=mocks/mock_user_repository.go -package=mocks
import (
"testing"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"myapp/repository/mocks"
)
func TestUserService_GetUser(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
// 创建模拟
mockRepo := mocks.NewMockUserRepository(ctrl)
// 设置期望
expectedUser := &User{ID: 1, Name: "Alice"}
mockRepo.EXPECT().
GetByID(1).
Return(expectedUser, nil).
Times(1)
// 测试
service := NewUserService(mockRepo)
user, err := service.GetUser(1)
// 断言
assert.NoError(t, err)
assert.Equal(t, expectedUser, user)
}
func TestUserService_CreateUser_Validation(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockRepo := mocks.NewMockUserRepository(ctrl)
// 期望 Create 不被调用(验证应该首先失败)
mockRepo.EXPECT().Create(gomock.Any()).Times(0)
service := NewUserService(mockRepo)
err := service.CreateUser(&User{Name: ""}) // 无效用户
assert.Error(t, err)
assert.Contains(t, err.Error(), "name is required")
}
// 用于复杂验证的自定义匹配器
type userMatcher struct {
expectedEmail string
}
func (m userMatcher) Matches(x interface{}) bool {
user, ok := x.(*User)
if !ok {
return false
}
return user.Email == m.expectedEmail
}
func (m userMatcher) String() string {
return "matches user with email: " + m.expectedEmail
}
func UserWithEmail(email string) gomock.Matcher {
return userMatcher{expectedEmail: email}
}
// 在测试中使用
func TestCustomMatcher(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockRepo := mocks.NewMockUserRepository(ctrl)
mockRepo.EXPECT().
Create(UserWithEmail("alice@example.com")).
Return(nil)
service := NewUserService(mockRepo)
service.CreateUser(&User{Name: "Alice", Email: "alice@example.com"})
}
func BenchmarkAdd(b *testing.B) {
calc := NewCalculator()
for i := 0; i < b.N; i++ {
calc.Add(2, 3)
}
}
func BenchmarkStringConcatenation(b *testing.B) {
b.Run("plus operator", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = "hello" + "world"
}
})
b.Run("strings.Builder", func(b *testing.B) {
for i := 0; i < b.N; i++ {
var sb strings.Builder
sb.WriteString("hello")
sb.WriteString("world")
_ = sb.String()
}
})
}
# 运行所有基准测试
go test -bench=.
# 运行特定基准测试
go test -bench=BenchmarkAdd
# 包含内存分配统计
go test -bench=. -benchmem
# 比较基准测试
go test -bench=. -benchmem > old.txt
# 进行更改
go test -bench=. -benchmem > new.txt
benchstat old.txt new.txt
BenchmarkAdd-8 1000000000 0.25 ns/op 0 B/op 0 allocs/op
BenchmarkStringBuilder-8 50000000 28.5 ns/op 64 B/op 1 allocs/op
解读:50000000 次迭代,每次操作 28.5 ns/op,每次操作分配 64 B/op 字节,每次操作 1 allocs/op 次分配
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestUserHandler(t *testing.T) {
handler := http.HandlerFunc(UserHandler)
req := httptest.NewRequest("GET", "/users/1", nil)
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
assert.Equal(t, http.StatusOK, rec.Code)
assert.Contains(t, rec.Body.String(), "Alice")
}
func TestHTTPClient(t *testing.T) {
// 模拟服务器
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "/api/users", r.URL.Path)
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"id": 1, "name": "Alice"}`))
}))
defer server.Close()
// 针对模拟服务器测试客户端
client := NewAPIClient(server.URL)
user, err := client.GetUser(1)
assert.NoError(t, err)
assert.Equal(t, "Alice", user.Name)
}
检测并发代码中的数据竞争:
go test -race ./...
并发安全性测试示例:
func TestConcurrentMapAccess(t *testing.T) {
cache := NewSafeCache()
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func(val int) {
defer wg.Done()
cache.Set(fmt.Sprintf("key%d", val), val)
}(i)
}
wg.Wait()
assert.Equal(t, 100, cache.Len())
}
针对预期输出文件进行测试:
func TestRenderTemplate(t *testing.T) {
output := RenderTemplate("user", User{Name: "Alice"})
goldenFile := "testdata/user_template.golden"
if *update {
// 更新黄金文件:go test -update
os.WriteFile(goldenFile, []byte(output), 0644)
}
expected, err := os.ReadFile(goldenFile)
require.NoError(t, err)
assert.Equal(t, string(expected), output)
}
var update = flag.Bool("update", false, "update golden files")
# .github/workflows/test.yml
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.23'
- name: Run tests
run: go test -v -race -coverprofile=coverage.out ./...
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: ./coverage.out
- name: Check coverage threshold
run: |
go tool cover -func=coverage.out | grep total | awk '{print $3}' | sed 's/%//' | \
awk '{if ($1 < 80) exit 1}'
# 生成覆盖率报告
go test -coverprofile=coverage.out ./...
# 在终端中查看覆盖率
go tool cover -func=coverage.out
# 生成 HTML 报告
go tool cover -html=coverage.out -o coverage.html
# 检查覆盖率阈值(如果 < 80% 则失败)
go test -coverprofile=coverage.out ./... && \
go tool cover -func=coverage.out | grep total | awk '{if (substr($3, 1, length($3)-1) < 80) exit 1}'
在以下情况下使用标准 testing 包:
在以下情况下使用 Testify:
assert.Equal 对比冗长的检查)在以下情况下使用 Gomock:
在以下情况下使用基准测试:
在以下情况下使用 httptest:
在以下情况下使用竞态检测器:
❌ 不要模拟所有东西
// 错误:过度模拟使测试脆弱
mockLogger := mocks.NewMockLogger(ctrl)
mockConfig := mocks.NewMockConfig(ctrl)
mockMetrics := mocks.NewMockMetrics(ctrl)
// 太多模拟 = 脆弱的测试
✅ 应该:只模拟外部依赖
// 正确:只模拟数据库,使用真实的 logger/config
mockRepo := mocks.NewMockUserRepository(ctrl)
service := NewUserService(mockRepo, realLogger, realConfig)
❌ 不要测试实现细节
// 错误:测试内部状态
assert.Equal(t, "processing", service.internalState)
✅ 应该:测试公共行为
// 正确:测试可观察的结果
user, err := service.GetUser(1)
assert.NoError(t, err)
assert.Equal(t, "Alice", user.Name)
❌ 不要忽略错误情况
// 错误:只测试成功路径
func TestGetUser(t *testing.T) {
user, _ := service.GetUser(1) // 忽略错误!
assert.NotNil(t, user)
}
✅ 应该:测试错误条件
// 正确:测试成功和错误情况
func TestGetUser_NotFound(t *testing.T) {
user, err := service.GetUser(999)
assert.Error(t, err)
assert.Nil(t, user)
assert.Contains(t, err.Error(), "not found")
}
*_test.go 文件与源代码放在一起t.Run() 组织相关测试t.Parallel()-race-short 标志进行快速运行官方文档:
测试框架:
最新指南(2025):
相关技能:
go test ./... # 所有测试
go test -v ./... # 详细输出
go test -short ./... # 跳过慢速测试
go test -run TestUserCreate # 特定测试
go test -race ./... # 使用竞态检测器
go test -cover ./... # 使用覆盖率
go test -coverprofile=c.out ./... # 覆盖率文件
go test -bench=. -benchmem # 包含内存的基准测试
go generate ./... # 所有 //go:generate 指令
mockgen -source=interface.go -destination=mock.go
go tool cover -func=coverage.out # 每个函数的覆盖率
go tool cover -html=coverage.out # HTML 报告
令牌估算 : ~4,500 令牌(入口点 + 完整内容) 版本 : 1.0.0 最后更新 : 2025-12-03
每周安装数
158
仓库
GitHub 星标数
18
首次出现
Jan 23, 2026
安全审计
安装于
opencode128
gemini-cli122
codex122
github-copilot114
claude-code112
cursor106
Go provides a robust built-in testing framework (testing package) that emphasizes simplicity and developer productivity. Combined with community tools like testify and gomock, Go testing enables comprehensive test coverage with minimal boilerplate.
Key Features:
Activate this skill when:
t.Run()*_test.go files alongside codepackage_test for external tests, package for internalFile Naming Convention:
file_test.gofile_integration_test.goBenchmark in same test filePackage Structure:
mypackage/
├── user.go
├── user_test.go // Internal tests (same package)
├── user_external_test.go // External tests (package mypackage_test)
├── integration_test.go // Integration tests
└── testdata/ // Test fixtures (ignored by go build)
└── golden.json
The idiomatic Go testing pattern for testing multiple inputs:
func TestUserValidation(t *testing.T) {
tests := []struct {
name string
input User
wantErr bool
errMsg string
}{
{
name: "valid user",
input: User{Name: "Alice", Age: 30, Email: "alice@example.com"},
wantErr: false,
},
{
name: "empty name",
input: User{Name: "", Age: 30, Email: "alice@example.com"},
wantErr: true,
errMsg: "name is required",
},
{
name: "invalid email",
input: User{Name: "Bob", Age: 25, Email: "invalid"},
wantErr: true,
errMsg: "invalid email format",
},
{
name: "negative age",
input: User{Name: "Charlie", Age: -5, Email: "charlie@example.com"},
wantErr: true,
errMsg: "age must be positive",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := ValidateUser(tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("ValidateUser() error = %v, wantErr %v", err, tt.wantErr)
return
}
if tt.wantErr && err.Error() != tt.errMsg {
t.Errorf("ValidateUser() error message = %v, want %v", err.Error(), tt.errMsg)
}
})
}
}
Enable parallel test execution for independent tests:
func TestConcurrentOperations(t *testing.T) {
tests := []struct {
name string
fn func() int
want int
}{
{"operation 1", func() int { return compute1() }, 42},
{"operation 2", func() int { return compute2() }, 84},
{"operation 3", func() int { return compute3() }, 126},
}
for _, tt := range tests {
tt := tt // Capture range variable
t.Run(tt.name, func(t *testing.T) {
t.Parallel() // Run tests concurrently
got := tt.fn()
if got != tt.want {
t.Errorf("got %v, want %v", got, tt.want)
}
})
}
}
go get github.com/stretchr/testify
Replace verbose error checking with readable assertions:
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCalculator(t *testing.T) {
calc := NewCalculator()
// assert: Test continues on failure
assert.Equal(t, 5, calc.Add(2, 3))
assert.NotNil(t, calc)
assert.True(t, calc.IsReady())
// require: Test stops on failure (for critical assertions)
result, err := calc.Divide(10, 2)
require.NoError(t, err) // Stop if error occurs
assert.Equal(t, 5, result)
}
func TestUserOperations(t *testing.T) {
user := &User{ID: 1, Name: "Alice", Email: "alice@example.com"}
// Object matching
assert.Equal(t, 1, user.ID)
assert.Contains(t, user.Email, "@")
assert.Len(t, user.Name, 5)
// Partial matching
assert.ObjectsAreEqual(user, &User{
ID: 1,
Name: "Alice",
Email: assert.AnythingOfType("string"),
})
}
Organize related tests with setup/teardown:
import (
"testing"
"github.com/stretchr/testify/suite"
)
type UserServiceTestSuite struct {
suite.Suite
db *sql.DB
service *UserService
}
// SetupSuite runs once before all tests
func (s *UserServiceTestSuite) SetupSuite() {
s.db = setupTestDatabase()
s.service = NewUserService(s.db)
}
// TearDownSuite runs once after all tests
func (s *UserServiceTestSuite) TearDownSuite() {
s.db.Close()
}
// SetupTest runs before each test
func (s *UserServiceTestSuite) SetupTest() {
cleanDatabase(s.db)
}
// TearDownTest runs after each test
func (s *UserServiceTestSuite) TearDownTest() {
// Cleanup if needed
}
// Test methods must start with "Test"
func (s *UserServiceTestSuite) TestCreateUser() {
user := &User{Name: "Alice", Email: "alice@example.com"}
err := s.service.Create(user)
s.NoError(err)
s.NotEqual(0, user.ID) // ID assigned
}
func (s *UserServiceTestSuite) TestGetUser() {
// Setup
user := &User{Name: "Bob", Email: "bob@example.com"}
s.service.Create(user)
// Test
retrieved, err := s.service.GetByID(user.ID)
s.NoError(err)
s.Equal(user.Name, retrieved.Name)
}
// Run the suite
func TestUserServiceTestSuite(t *testing.T) {
suite.Run(t, new(UserServiceTestSuite))
}
go install github.com/golang/mock/mockgen@latest
// user_repository.go
package repository
//go:generate mockgen -source=user_repository.go -destination=mocks/mock_user_repository.go -package=mocks
type UserRepository interface {
GetByID(id int) (*User, error)
Create(user *User) error
Update(user *User) error
Delete(id int) error
}
Generate mocks:
go generate ./...
# Or manually:
mockgen -source=user_repository.go -destination=mocks/mock_user_repository.go -package=mocks
import (
"testing"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"myapp/repository/mocks"
)
func TestUserService_GetUser(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
// Create mock
mockRepo := mocks.NewMockUserRepository(ctrl)
// Set expectations
expectedUser := &User{ID: 1, Name: "Alice"}
mockRepo.EXPECT().
GetByID(1).
Return(expectedUser, nil).
Times(1)
// Test
service := NewUserService(mockRepo)
user, err := service.GetUser(1)
// Assertions
assert.NoError(t, err)
assert.Equal(t, expectedUser, user)
}
func TestUserService_CreateUser_Validation(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockRepo := mocks.NewMockUserRepository(ctrl)
// Expect Create to NOT be called (validation should fail first)
mockRepo.EXPECT().Create(gomock.Any()).Times(0)
service := NewUserService(mockRepo)
err := service.CreateUser(&User{Name: ""}) // Invalid user
assert.Error(t, err)
assert.Contains(t, err.Error(), "name is required")
}
// Custom matcher for complex validation
type userMatcher struct {
expectedEmail string
}
func (m userMatcher) Matches(x interface{}) bool {
user, ok := x.(*User)
if !ok {
return false
}
return user.Email == m.expectedEmail
}
func (m userMatcher) String() string {
return "matches user with email: " + m.expectedEmail
}
func UserWithEmail(email string) gomock.Matcher {
return userMatcher{expectedEmail: email}
}
// Usage in test
func TestCustomMatcher(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockRepo := mocks.NewMockUserRepository(ctrl)
mockRepo.EXPECT().
Create(UserWithEmail("alice@example.com")).
Return(nil)
service := NewUserService(mockRepo)
service.CreateUser(&User{Name: "Alice", Email: "alice@example.com"})
}
func BenchmarkAdd(b *testing.B) {
calc := NewCalculator()
for i := 0; i < b.N; i++ {
calc.Add(2, 3)
}
}
func BenchmarkStringConcatenation(b *testing.B) {
b.Run("plus operator", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = "hello" + "world"
}
})
b.Run("strings.Builder", func(b *testing.B) {
for i := 0; i < b.N; i++ {
var sb strings.Builder
sb.WriteString("hello")
sb.WriteString("world")
_ = sb.String()
}
})
}
# Run all benchmarks
go test -bench=.
# Run specific benchmark
go test -bench=BenchmarkAdd
# With memory allocation stats
go test -bench=. -benchmem
# Compare benchmarks
go test -bench=. -benchmem > old.txt
# Make changes
go test -bench=. -benchmem > new.txt
benchstat old.txt new.txt
BenchmarkAdd-8 1000000000 0.25 ns/op 0 B/op 0 allocs/op
BenchmarkStringBuilder-8 50000000 28.5 ns/op 64 B/op 1 allocs/op
Reading: 50000000 iterations, 28.5 ns/op per operation, 64 B/op bytes allocated per op, 1 allocs/op allocations per op
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestUserHandler(t *testing.T) {
handler := http.HandlerFunc(UserHandler)
req := httptest.NewRequest("GET", "/users/1", nil)
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
assert.Equal(t, http.StatusOK, rec.Code)
assert.Contains(t, rec.Body.String(), "Alice")
}
func TestHTTPClient(t *testing.T) {
// Mock server
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "/api/users", r.URL.Path)
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"id": 1, "name": "Alice"}`))
}))
defer server.Close()
// Test client against mock server
client := NewAPIClient(server.URL)
user, err := client.GetUser(1)
assert.NoError(t, err)
assert.Equal(t, "Alice", user.Name)
}
Detect data races in concurrent code:
go test -race ./...
Example test for concurrent safety:
func TestConcurrentMapAccess(t *testing.T) {
cache := NewSafeCache()
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func(val int) {
defer wg.Done()
cache.Set(fmt.Sprintf("key%d", val), val)
}(i)
}
wg.Wait()
assert.Equal(t, 100, cache.Len())
}
Test against expected output files:
func TestRenderTemplate(t *testing.T) {
output := RenderTemplate("user", User{Name: "Alice"})
goldenFile := "testdata/user_template.golden"
if *update {
// Update golden file: go test -update
os.WriteFile(goldenFile, []byte(output), 0644)
}
expected, err := os.ReadFile(goldenFile)
require.NoError(t, err)
assert.Equal(t, string(expected), output)
}
var update = flag.Bool("update", false, "update golden files")
# .github/workflows/test.yml
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.23'
- name: Run tests
run: go test -v -race -coverprofile=coverage.out ./...
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: ./coverage.out
- name: Check coverage threshold
run: |
go tool cover -func=coverage.out | grep total | awk '{print $3}' | sed 's/%//' | \
awk '{if ($1 < 80) exit 1}'
# Generate coverage report
go test -coverprofile=coverage.out ./...
# View coverage in terminal
go tool cover -func=coverage.out
# Generate HTML report
go tool cover -html=coverage.out -o coverage.html
# Check coverage threshold (fail if < 80%)
go test -coverprofile=coverage.out ./... && \
go tool cover -func=coverage.out | grep total | awk '{if (substr($3, 1, length($3)-1) < 80) exit 1}'
Use Standardtesting Package When:
Use Testify When:
assert.Equal vs verbose checks)Use Gomock When:
Use Benchmarks When:
Use httptest When:
Use Race Detector When:
❌ Don't Mock Everything
// WRONG: Over-mocking makes tests brittle
mockLogger := mocks.NewMockLogger(ctrl)
mockConfig := mocks.NewMockConfig(ctrl)
mockMetrics := mocks.NewMockMetrics(ctrl)
// Too many mocks = fragile test
✅ Do: Mock Only External Dependencies
// CORRECT: Mock only database, use real logger/config
mockRepo := mocks.NewMockUserRepository(ctrl)
service := NewUserService(mockRepo, realLogger, realConfig)
❌ Don't Test Implementation Details
// WRONG: Testing internal state
assert.Equal(t, "processing", service.internalState)
✅ Do: Test Public Behavior
// CORRECT: Test observable outcomes
user, err := service.GetUser(1)
assert.NoError(t, err)
assert.Equal(t, "Alice", user.Name)
❌ Don't Ignore Error Cases
// WRONG: Only testing happy path
func TestGetUser(t *testing.T) {
user, _ := service.GetUser(1) // Ignoring error!
assert.NotNil(t, user)
}
✅ Do: Test Error Conditions
// CORRECT: Test both success and error cases
func TestGetUser_NotFound(t *testing.T) {
user, err := service.GetUser(999)
assert.Error(t, err)
assert.Nil(t, user)
assert.Contains(t, err.Error(), "not found")
}
*_test.go files alongside source codet.Run()t.Parallel() for independent tests-race for concurrent code-short flag for quick runsOfficial Documentation:
Testing Frameworks:
Recent Guides (2025):
Related Skills:
go test ./... # All tests
go test -v ./... # Verbose output
go test -short ./... # Skip slow tests
go test -run TestUserCreate # Specific test
go test -race ./... # With race detector
go test -cover ./... # With coverage
go test -coverprofile=c.out ./... # Coverage file
go test -bench=. -benchmem # Benchmarks with memory
go generate ./... # All //go:generate directives
mockgen -source=interface.go -destination=mock.go
go tool cover -func=coverage.out # Coverage per function
go tool cover -html=coverage.out # HTML report
Token Estimate : ~4,500 tokens (entry point + full content) Version : 1.0.0 Last Updated : 2025-12-03
Weekly Installs
158
Repository
GitHub Stars
18
First Seen
Jan 23, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode128
gemini-cli122
codex122
github-copilot114
claude-code112
cursor106
测试策略完整指南:单元/集成/E2E测试金字塔与自动化实践
11,200 周安装
Go 代码风格核心原则与最佳实践 | Google/Uber 权威指南
331 周安装
Cloudflare Vectorize 完整指南:全球分布式向量数据库,实现语义搜索与RAG应用
326 周安装
Cloudflare Agents SDK:构建AI驱动的自主智能体,支持可恢复流式传输与持久化状态
326 周安装
Snowflake平台技能:使用CLI、Cortex AI函数和Snowpark构建AI数据云应用
326 周安装
React + Cloudflare Workers 集成 Microsoft Entra ID 身份验证完整指南 | Azure Auth
327 周安装
Cloudflare Images 图像托管与转换 API 使用指南 | 支持 AI 人脸裁剪与内容凭证
328 周安装