学习目标
- 理解 Go 官方 testing 包的设计哲学
- 掌握表驱动测试的标准写法
- 学会通过接口进行 mock 设计
- 建立“测试是工程资产”的认知
一、为什么 Go 如此重视测试
在 Go 的工程文化中:
- 测试是语言级支持的一部分
- 不是第三方框架附加能力
Go 官方提供:
testing:单元测试testing/quick:属性测试go test:统一测试入口
工程认知:可测试性是代码质量的直接体现。
二、testing 包的基本结构
一个最基本的测试:
func TestAdd(t *testing.T) {
result := Add(1, 2)
if result != 3 {
t.Fatalf("expect 3, got %d", result)
}
}基本规则:
- 文件名以
_test.go结尾 - 测试函数以
Test开头 - 参数必须是
*testing.T
三、表驱动测试(Table-Driven Test)
Go 社区最推荐的测试方式。
示例:
func TestAdd_Table(t *testing.T) {
tests := []struct {
name string
a, b int
expect int
}{
{"both positive", 1, 2, 3},
{"with zero", 0, 5, 5},
{"negative", -1, 1, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := Add(tt.a, tt.b); got != tt.expect {
t.Fatalf("expect %d, got %d", tt.expect, got)
}
})
}
}工程价值:
- 减少重复代码
- 测试用例结构清晰
- 便于新增测试场景
四、子测试(t.Run)的意义
t.Run 的作用:
- 将一个测试拆成多个子场景
- 失败定位更精确
配合表驱动测试,是 Go 测试的标准形态。
工程结论:一个 Test 函数,代表一个“行为”;子测试代表“场景”。
五、测试中的失败策略
常用方法:
t.Errorf:记录错误,继续执行t.Fatalf:立即失败,终止当前测试
工程建议:
- 前置条件失败,用 Fatal
- 结果校验失败,用 Errorf 或 Fatalf
六、可测试性设计:接口优先
测试困难,通常不是 testing 的问题,而是设计问题。
示例:
type Store interface {
Get(id int) (Item, error)
}业务逻辑只依赖接口:
func Service(s Store) error {
_, err := s.Get(1)
return err
}工程价值:
- 可以轻松 mock
- 无需真实 DB / RPC
七、mock 的工程策略
Go 社区更偏好:
- 手写 mock
- 而不是重度依赖 mock 框架
简单 mock 示例:
type mockStore struct {
err error
}
func (m *mockStore) Get(id int) (Item, error) {
return Item{}, m.err
}工程结论:
mock 是设计自然产生的副产品,而不是强行引入的工具。
八、测试中的并发注意事项
测试中使用 goroutine 时:
- 要特别注意数据竞争
- 测试本身也会并发执行
推荐:
- 配合
go test -race - 避免共享可变全局状态
九、单元测试 vs 集成测试
单元测试:
- 关注函数 / 模块行为
- 速度快、定位准
集成测试:
- 关注模块协作
- 验证系统边界
工程建议:
单元测试为主,集成测试兜底。
十、测试驱动的工程价值
- 测试迫使你写出可组合的代码
- 测试是最好的文档
- 测试让重构更安全
工程结论:测试不是成本,是长期收益。
十一、测试体系总结
- testing 包简单但强大
- 表驱动测试是标准实践
- 接口设计决定测试难度
一句话总结:
写不好测试,往往是设计已经出了问题。
预告:Day 28|构建、发布与 CI/CD 基础
下一天将系统讲解:
- Go 构建流程与交叉编译
- 版本号与构建信息注入
- CI 中的 go test / go build
- 工程级发布流程