Go 的内置测试框架、代码规范与工程化实践
一、单元测试
1.1 测试文件命名
// 测试文件以 _test.go 结尾
math.go → math_test.go
user.go → user_test.go
utils/string.go → utils/string_test.go
1.2 基本测试函数
// math.go
package math
func Add(a, b int) int {
return a + b
}
// math_test.go
package math
import "testing"
func TestAdd(t *testing.T) {
result := Add(1, 2)
if result != 3 {
t.Errorf("Add(1, 2) = %d; want 3", result)
}
}
// 运行测试
// go test ./...
// go test -v ./...(详细输出)
1.3 Table-Driven Tests(表驱动测试)
Go 社区最推崇的测试模式:
func TestAdd(t *testing.T) {
tests := []struct {
name string
a, b int
expected int
}{
{"positive numbers", 1, 2, 3},
{"negative numbers", -1, -2, -3},
{"zero", 0, 0, 0},
{"mixed", -1, 1, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := Add(tt.a, tt.b)
if result != tt.expected {
t.Errorf("Add(%d, %d) = %d; want %d",
tt.a, tt.b, result, tt.expected)
}
})
}
}
// 运行特定子测试
// go test -run TestAdd/positive_numbers
1.4 测试辅助函数
// 断言辅助函数(Go 标准库没有内置 assert,但可以自己写)
func assertEqual(t *testing.T, got, want interface{}) {
t.Helper() // 标记为辅助函数,错误信息显示调用者行号
if got != want {
t.Errorf("got %v, want %v", got, want)
}
}
func TestAdd(t *testing.T) {
assertEqual(t, Add(1, 2), 3)
}
t.Helper():标记辅助函数,错误时显示调用方的行号而非辅助函数内部行号。
二、测试进阶
2.1 TestMain — 测试入口
func TestMain(m *testing.M) {
// 测试前的全局设置
setup()
// 运行所有测试
code := m.Run()
// 测试后的清理
teardown()
os.Exit(code)
}
2.2 基准测试(Benchmark)
func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
Add(1, 2)
}
}
// 运行
// go test -bench=. -benchmem
// 输出:BenchmarkAdd-8 1000000000 0.28 ns/op 0 B/op 0 allocs/op
// 带缓存的基准测试
func BenchmarkAddCached(b *testing.B) {
a, result := 1, 2 // 预设值
b.ResetTimer() // 重置计时器
for i := 0; i < b.N; i++ {
Add(a, result)
}
}
2.3 示例测试(Example)
// Example 既是测试,也是文档(会出现在 godoc 中)
func ExampleAdd() {
result := Add(1, 2)
fmt.Println(result)
// Output: 3
}
func ExampleAdd_negative() {
result := Add(-1, -2)
fmt.Println(result)
// Output: -3
}
// 运行
// go test -run Example
2.4 测试覆盖率
// 查看覆盖率
// go test -cover ./...
// 生成覆盖率报告
// go test -coverprofile=coverage.out ./...
// go tool cover -html=coverage.out -o coverage.html
2.5 并行测试
func TestAdd(t *testing.T) {
tests := []struct{ a, b, want int }{
{1, 2, 3}, {4, 5, 9},
}
for _, tt := range tests {
t.Run("...", func(t *testing.T) {
t.Parallel() // 标记为可并行运行
result := Add(tt.a, tt.b)
if result != tt.want {
t.Errorf("got %d, want %d", result, tt.want)
}
})
}
}
三、Mock 与依赖注入
3.1 接口驱动的 Mock
// 定义接口(消费方)
type UserStore interface {
FindByID(id int) (*User, error)
}
// 实现(生产方)
type MySQLStore struct{ db *sql.DB }
func (s *MySQLStore) FindByID(id int) (*User, error) { ... }
// 测试用 Mock
type MockStore struct {
Users map[int]*User
}
func (m *MockStore) FindByID(id int) (*User, error) {
user, ok := m.Users[id]
if !ok {
return nil, ErrNotFound
}
return user, nil
}
// Service 依赖接口
type UserService struct {
store UserStore
}
func (s *UserService) GetUser(id int) (*User, error) {
return s.store.FindByID(id)
}
// 测试
func TestGetUser(t *testing.T) {
mock := &MockStore{
Users: map[int]*User{1: {ID: 1, Name: "Alice"}},
}
svc := &UserService{store: mock}
user, err := svc.GetUser(1)
if err != nil {
t.Fatal(err)
}
if user.Name != "Alice" {
t.Errorf("got %s, want Alice", user.Name)
}
}
3.2 第三方 Mock 库
// testify/mock — 常用的 mock 框架
import "github.com/stretchr/testify/mock"
type MockStore struct {
mock.Mock
}
func (m *MockStore) FindByID(id int) (*User, error) {
args := m.Called(id)
return args.Get(0).(*User), args.Error(1)
}
func TestGetUser(t *testing.T) {
m := new(MockStore)
m.On("FindByID", 1).Return(&User{Name: "Alice"}, nil)
svc := &UserService{store: m}
user, _ := svc.GetUser(1)
m.AssertExpectations(t)
}
四、Go 代码规范
4.1 命名规范
// 包名:小写,简短,不下划线
package httpclient // ✅
package http_client // ❌
// 导出标识符:大写驼峰
func GetUserData() {} // ✅
func Get_user_data() {} // ❌
// 非导出标识符:小写驼峰
func validateInput() {} // ✅
func ValidateInput() {} // 只在需要导出时用大写
// 接口命名:方法少的用 -er 后缀
type Reader interface { Read(p []byte) (n int, err error) }
type Writer interface { Write(p []byte) (n int, err error) }
type Stringer interface { String() string }
4.2 格式化与工具
// go fmt — 统一代码格式(不需要讨论缩进/花括号位置)
// go vet — 静态分析,发现常见错误
// golangci-lint — 集成多种 lint 工具
// 安装 golangci-lint
// go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
// 运行
// golangci-lint run ./...
4.3 go generate
// 在代码中嵌入生成指令
//go:generate stringer -type=MyType
// 运行所有生成指令
// go generate ./...
4.4 代码组织原则
// 每个文件不要太大,通常 200-400 行
// 如果一个文件超过 500 行,考虑拆分
// 包的设计原则:
// 1. 包应该小而专注(单一职责)
// 2. 避免循环依赖
// 3. 接口在消费方定义
// 4. 导出的 API 要精心设计(一旦导出,就要保持兼容)
五、项目构建与发布
5.1 编译优化
// 基本编译
go build -o myapp
// 去除调试信息(减小二进制大小)
go build -ldflags="-s -w" -o myapp
// 注入版本信息
go build -ldflags="-X main.Version=1.0.0 -X main.BuildTime=2024-01-01" -o myapp
5.2 交叉编译
// 交叉编译不需要 CGO 的项目非常简单
// Linux -> Windows
GOOS=windows GOARCH=amd64 go build -o myapp.exe
// 所有平台组合
GOOS=linux GOARCH=amd64 go build -o myapp-linux-amd64
GOOS=linux GOARCH=arm64 go build -o myapp-linux-arm64
GOOS=darwin GOARCH=arm64 go build -o myapp-darwin-arm64
GOOS=windows GOARCH=amd64 go build -o myapp-windows-amd64.exe
5.3 Makefile 示例
.PHONY: build test lint clean
build:
go build -ldflags="-s -w" -o bin/myapp
test:
go test -v -race ./...
lint:
golangci-lint run ./...
clean:
rm -rf bin/
# 交叉编译所有平台
build-all:
GOOS=linux GOARCH=amd64 go build -o bin/myapp-linux-amd64
GOOS=darwin GOARCH=arm64 go build -o bin/myapp-darwin-arm64
GOOS=windows GOARCH=amd64 go build -o bin/myapp-windows-amd64.exe
六、常用工具速查
| 工具 | 命令 | 作用 |
|---|---|---|
go test | go test -v -race ./... | 运行测试 |
go test -cover | go test -cover ./... | 查看覆盖率 |
go test -bench | go test -bench=. -benchmem | 运行基准测试 |
go vet | go vet ./... | 静态分析 |
go fmt | go fmt ./... | 代码格式化 |
gofmt | gofmt -d . | 查看格式差异 |
goimports | goimports -d . | 自动管理 import |
golangci-lint | golangci-lint run | 综合 lint |
go tool pprof | go tool pprof | 性能分析 |
go generate | go generate ./... | 运行代码生成 |
go doc | go doc fmt.Println | 查看文档 |
go list | go list -m all | 列出模块依赖 |
七、学习阶段一总结
至此,Go 基础语法的 10 篇笔记全部完成:
| 篇目 | 核心内容 |
|---|---|
| 01-环境与工具链 | 安装配置、Go Modules、核心命令 |
| 02-变量与类型 | 变量声明、基本类型、类型转换 |
| 03-复合类型 | 切片、Map、结构体、指针 |
| 04-流程控制 | if/for/switch/defer |
| 05-错误处理 | error 接口、panic/recover、错误链 |
| 06-函数与方法 | 多返回值、闭包、方法接收者 |
| 07-接口与多态 | 隐式接口、空接口、面向接口编程 |
| 08-包与模块 | 包组织、可见性、项目结构 |
| 09-并发编程基础 | Goroutine、Channel、Select、sync |
| 10-测试与工程实践 | 单元测试、基准测试、代码规范 |
豫公网安备41108202000982号