Go之内置测试包
,供以简约之框架,无外倚,可作单元测试之要务。
兹列测试之根本,项目之结构,及高深之范式,以助构建可靠之Go应用。
Go中测试之要义
Go之理念,重简约而求可靠。标准库中含testing 之包,使单元测试为 Go 生态之翘楚。经严测之 Go 代码,增可维护,早捕谬误,且以例示为文。初涉 Go 者欲速览其要,可检视吾辈所撰 Go 速查表。
Go 测试之要益:
- 内置支持:无需外部框架
- :执行迅捷:默认并行测试执行
- :语法简明:样板代码极简
- :工具有余:涵盖报告、基准测试及性能分析
- :适配持续集成与持续部署:易于融入自动化流程
Go测试项目结构
Go测试与生产代码共存,命名规范明晰:
myproject/
├── go.mod
├── main.go
├── calculator.go
├── calculator_test.go
├── utils/
│ ├── helper.go
│ └── helper_test.go
└── models/
├── user.go
└── user_test.go
主要规范:
- 测试文件以
_test.go - 结尾
_test测试与代码同包(或用__JHSNS_SEG_e6813b5e_26__后缀进行黑盒测试) - 每源文件,皆可有其对应的测试文件
软件包测试之法
白箱测试(同软件包):
package calculator
import "testing"
// Can access unexported functions and variables
黑箱测试(外部软件包):
package calculator_test
import (
"testing"
"myproject/calculator"
)
// Can only access exported functions (recommended for public APIs)
基本测试结构
每测试函数皆循此式:
package calculator
import "testing"
// Test function must start with "Test"
func TestAdd(t *testing.T) {
result := Add(2, 3)
expected := 5
if result != expected {
t.Errorf("Add(2, 3) = %d; want %d", result, expected)
}
}
Testing.T方法:
-
t.Error()/t.Errorf():标测试为败而续行 -
t.Fatal()/t.Fatalf():标测试为败而立止 -
t.Log()/t.Logf():日志输出(唯-v标志显之) -
t.Skip()/t.Skipf():略试 -
t.Parallel():并行试诸试
t.Log乃为可读之试辨。运行之务,log/slog及JSON善容之记,常宜于聚汇与事弊之查。详见以slog在Go中实现结构化日志,用于可观测性与告警.
表驱动测试:Go之正道
表驱动测试乃Go中测试多场景之惯用之法。借Go泛型之力,亦可创制类型安全之测试助手,通行于异类数据之域:
func TestCalculate(t *testing.T) {
tests := []struct {
name string
a, b int
op string
expected int
wantErr bool
}{
{"addition", 2, 3, "+", 5, false},
{"subtraction", 5, 3, "-", 2, false},
{"multiplication", 4, 3, "*", 12, false},
{"division", 10, 2, "/", 5, false},
{"division by zero", 10, 0, "/", 0, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := Calculate(tt.a, tt.b, tt.op)
if (err != nil) != tt.wantErr {
t.Errorf("Calculate() error = %v, wantErr %v", err, tt.wantErr)
return
}
if result != tt.expected {
t.Errorf("Calculate(%d, %d, %q) = %d; want %d",
tt.a, tt.b, tt.op, result, tt.expected)
}
})
}
}
优势:
- 一函数试多境
- 添新试案易
- 行文明晰待行
- 试组井然可持
运试
基本命令
# Run tests in current directory
go test
# Run tests with verbose output
go test -v
# Run tests in all subdirectories
go test ./...
# Run specific test
go test -run TestAdd
# Run tests matching pattern
go test -run TestCalculate/addition
# Run tests in parallel (default is GOMAXPROCS)
go test -parallel 4
# Run tests with timeout
go test -timeout 30s
测试覆盖率
# Run tests with coverage
go test -cover
# Generate coverage profile
go test -coverprofile=coverage.out
# View coverage in browser
go tool cover -html=coverage.out
# Show coverage by function
go tool cover -func=coverage.out
# Set coverage mode (set, count, atomic)
go test -covermode=count -coverprofile=coverage.out
有用标志
-
-short:运行标记if testing.Short()检查的测试 -
-race:启用竞争检测器(发现并发访问问题) -
-cpu:指定GOMAXPROCS值 -
-count n:运行每个测试n次 -
-failfast:首测失则止
测试助手与设置/拆除
助手函数
以t.Helper()标记助手函数,以增误报之明:
func assertEqual(t *testing.T, got, want int) {
t.Helper() // This line is reported as the caller
if got != want {
t.Errorf("got %d, want %d", got, want)
}
}
func TestMath(t *testing.T) {
result := Add(2, 3)
assertEqual(t, result, 5) // Error line points here
}
设置与拆除
func TestMain(m *testing.M) {
// Setup code here
setup()
// Run tests
code := m.Run()
// Teardown code here
teardown()
os.Exit(code)
}
测试固件
func setupTestCase(t *testing.T) func(t *testing.T) {
t.Log("setup test case")
return func(t *testing.T) {
t.Log("teardown test case")
}
}
func TestSomething(t *testing.T) {
teardown := setupTestCase(t)
defer teardown(t)
// Test code here
}
嘲弄与依赖注入
基于接口的模拟
试验与数据库交互之码,用接口可轻易造模拟实现。若尔于 Go 中用 PostgreSQL,请参吾等比较Go ORM 择数据库之良库,须具可测之能.
// Production code
type Database interface {
GetUser(id int) (*User, error)
}
type UserService struct {
db Database
}
func (s *UserService) GetUserName(id int) (string, error) {
user, err := s.db.GetUser(id)
if err != nil {
return "", err
}
return user.Name, nil
}
// Test code
type MockDatabase struct {
users map[int]*User
}
func (m *MockDatabase) GetUser(id int) (*User, error) {
if user, ok := m.users[id]; ok {
return user, nil
}
return nil, errors.New("user not found")
}
func TestGetUserName(t *testing.T) {
mockDB := &MockDatabase{
users: map[int]*User{
1: {ID: 1, Name: "Alice"},
},
}
service := &UserService{db: mockDB}
name, err := service.GetUserName(1)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if name != "Alice" {
t.Errorf("got %s, want Alice", name)
}
}
测试之库,众所习用
Testify
Go言测试之库,尤善于断言与模拟者.
import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
func TestWithTestify(t *testing.T) {
result := Add(2, 3)
assert.Equal(t, 5, result, "they should be equal")
assert.NotNil(t, result)
}
// Mock example
type MockDB struct {
mock.Mock
}
func (m *MockDB) GetUser(id int) (*User, error) {
args := m.Called(id)
return args.Get(0).(*User), args.Error(1)
}
他用之器
- gomock:谷歌之模拟框架,代码生成也
- httptest:测试HTTP处理器之标准库也
- testcontainers-go:以Docker容器为集成测试也
- ginkgo/gomega:行为驱动测试之框架也
試驗與外部服務如AI模型之整合,需虛擬或代換其依賴。譬如,若君以Ollama于Go,可思創介面之包覆,使君之碼更易試驗。
基準測試
Go含內建之基準測試支援:
func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
Add(2, 3)
}
}
// Run benchmarks
// go test -bench=. -benchmem
输出每秒迭代次数与内存分配
最佳实践
- 撰写表格驱动测试:为多个测试用例使用结构体切片模式
- 使用t.Run进行子测试:组织更优,可选择性运行子测试
- 先测试导出函数:专注公共接口之表现
- :测试务须简明:每试当验一事
- :测试名目须有义:述所试之实与所期之果
- :勿试施行之细:试其行,非其内
- :依界面以立依存:嘲弄易行
- 高覆盖率,然质重于量百不失一,非谓无瑕
- 以-race标志行测试早察竞态之患
- 以TestMain行繁重之设勿复设于每试
例:全试之套
package user
import (
"errors"
"testing"
)
type User struct {
ID int
Name string
Email string
}
func ValidateUser(u *User) error {
if u.Name == "" {
return errors.New("name cannot be empty")
}
if u.Email == "" {
return errors.New("email cannot be empty")
}
return nil
}
// Test file: user_test.go
func TestValidateUser(t *testing.T) {
tests := []struct {
name string
user *User
wantErr bool
errMsg string
}{
{
name: "valid user",
user: &User{ID: 1, Name: "Alice", Email: "alice@example.com"},
wantErr: false,
},
{
name: "empty name",
user: &User{ID: 1, Name: "", Email: "alice@example.com"},
wantErr: true,
errMsg: "name cannot be empty",
},
{
name: "empty email",
user: &User{ID: 1, Name: "Alice", Email: ""},
wantErr: true,
errMsg: "email cannot be empty",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := ValidateUser(tt.user)
if (err != nil) != tt.wantErr {
t.Errorf("ValidateUser() error = %v, wantErr %v", err, tt.wantErr)
return
}
if err != nil && err.Error() != tt.errMsg {
t.Errorf("ValidateUser() error message = %v, want %v", err.Error(), tt.errMsg)
}
})
}
}
有用之链
- 官方 Go 测试包文档
- Go 博客:表格驱动测试
- Testify GitHub 仓库
- GoMock 文档
- 以测试学 Go
- Go 代码覆盖率工具
- Go 梗概表
- 以slog于Go中构日志以察见警
- 较GoORM于PostgreSQL:GORM与Ent与Bun与sqlc
- Ollama之GoSDK比较并例证
- 以Cobra于Go中建CLI应用及 Viper
- Go泛型:用例与范式
结论
Go之测试框架,备齐万全,而设之简。循Go之常法,若驱表式之试,用介面以拟,用其内置之器,可成可持、可恃之试套,随代码之基而增。
此试之常法,施于诸般Go之用,自网务至CLI之用,建于Cobra者。与 Viper。测试命令行工具,需循此道而更重输入输出与标志解析之验。
始以简易之验,渐增覆盖之广,须知验之为道,实乃投资于代码之质与开发者之信。Go之社群重验,故持项目久远易,协众人之效亦佳。
睹之应用架构枢,,以寻关于Go项目结构、依赖注入、API设计及集成模式之相关指南。












