go测试核心在于边界设计、依赖隔离与并发控制,需遵循_test.go命名、同包原则,用接口注入替代硬mock,httptest.server替代真实http调用,testmain统一管理生命周期。

Go 语言原生的 testing 包足够写可靠、可维护的自动化测试脚本,不需要额外“工具”堆砌——真正卡住人的不是语法,而是测试边界、并发控制和依赖隔离。
用 go test 跑通基础单元测试
Go 的测试必须放在 _test.go 文件里,函数名以 Test 开头且接收 *testing.T 参数。别漏掉 go:test 的命名约定,否则 go test 直接忽略。
常见错误:go test 默认只跑当前目录下无子模块的测试;如果想递归跑所有子目录,得加 -r(即 go test -r ./...);但更推荐显式指定路径,比如 go test ./pkg/... -v,避免误触 vendor 或生成代码。
- 测试文件必须和被测代码在同一个包(
package xxx),不能跨包直接调用未导出函数 -
t.Fatal和t.Errorf的区别:前者立即终止当前测试函数,后者只记录错误继续执行,适合检查多个断言 - 用
go test -run=^TestLogin$精确匹配单个测试,正则支持防止名字冲突
模拟外部依赖:不用 monkey patch,用接口+构造函数注入
Go 没有运行时方法替换机制,硬 mock HTTP 或 DB 容易写出脆弱测试。正确做法是把依赖抽象成接口,测试时传入 fake 实现。
立即学习“go语言免费学习笔记(深入)”;
例如数据库操作:
type DB interface {
QueryRow(query string, args ...any) *sql.Row
}
func NewService(db DB) *Service { ... }
测试时传入自定义结构体:
type fakeDB struct{}
func (f fakeDB) QueryRow(_ string, _ ...any) *sql.Row {
return &sql.Row{} // 或返回预设错误
}
- 不要在测试里启动真实 PostgreSQL 或 Redis——哪怕用 Docker Compose,CI 上也容易超时或端口冲突
- HTTP 服务测试优先用
httptest.Server,而不是http.Get调真实地址 - 时间敏感逻辑(如过期判断)用
func() time.Time注入,测试时固定返回某个time.Time
并发测试要加 t.Parallel(),但注意数据竞争
多个测试函数加 t.Parallel() 可提速,但前提是它们不共享状态。一旦测试里用了全局变量、临时文件或共用 map,就会触发 go test -race 报错。
典型踩坑点:
- 在
init()里初始化全局 logger 或 config,导致多个测试并发修改同一对象 - 测试中用
os.Create("tmp.txt")写文件,没加随机后缀,多个测试互相覆盖 - 使用
sync.Pool时未重置对象字段,前一个测试塞的数据被后一个测试读到
验证方式很简单:go test -race -count=2 ./... 多跑几轮,比单次 -race 更容易暴露问题。
集成测试与测试夹具:用 testmain 控制生命周期
需要启动 DB、清理测试数据、或跑一次前置 setup 的场景,别在每个 TestXxx 里重复写 setup/teardown。改用 TestMain 函数统一管理:
func TestMain(m *testing.M) {
// setup: 启动测试 DB,迁移 schema
code := m.Run() // 执行所有 TestXxx
// teardown: 清空表,关闭连接
os.Exit(code)
}
注意:TestMain 必须调用 m.Run(),否则所有测试都不执行;而且它只在包级生效,子目录需各自定义。
- 避免在
TestMain里做耗时操作(如拉镜像、下载二进制),CI 上会拖慢整个测试套件 - 若测试依赖环境变量(如
TEST_DB_URL),在TestMain开头就检查,用t.Skip提前跳过,别让测试卡在 connect timeout - 用
go test -short标记跳过长耗时集成测试,对应代码里加if testing.Short() { t.Skip("skipping integration test") }
真正难的不是写 go test 命令,而是决定哪些逻辑值得测、哪些边界 case 会被忽略、以及当测试变慢时,第一反应是加 parallel 还是先查日志埋点——这些没法靠工具解决。










