go test 跑通不等于微服务代码可测,关键在于业务逻辑是否与 redis、http、数据库等 i/o 彻底隔离;未隔离则测试仅为“跑一遍”,而非验证逻辑。

go test 能跑通不等于微服务代码可测——真正的问题在于:业务逻辑是否与 Redis、HTTP、数据库等 I/O 彻底隔离?没做到这点,测试就只是“跑一遍”,不是“验证逻辑”。
测试前先确认代码是否可测
微服务里最常踩的坑是把依赖硬编码进函数里,比如 redis.Dial、http.Get 或直接 new 一个 DB 实例。这种写法导致测试时必须启动真实服务,结果不稳定、慢、CI 经常失败。
可测代码有三个明显特征:
- 业务函数接收接口参数(如
UserRepository),而不是自己创建连接 - 校验类逻辑(如
checkUsername)是纯函数:无副作用、不读环境变量、输入相同则输出确定 - 所有外部调用都抽象成接口,并通过构造函数或方法参数注入
如果 GetPersonDetail 函数里还藏着 client.Do("GET", ...),那就得先重构——否则写再多 TestGetPersonDetail 都只是集成测试。
用 gomock 生成并使用 Mock
手写 fake 实现容易漏方法、难同步接口变更,gomock 是唯一靠谱的选择。
安装和生成命令必须严格按顺序执行:
go install github.com/golang/mock/mockgen@latest mockgen -source=user_repo.go -destination=mocks/mock_user_repo.go -package=mocks
测试中关键点:
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
- 每个测试用例必须独立创建
ctrl := gomock.NewController(t),并在结尾加t.Cleanup(func() { ctrl.Finish() }),否则未满足的EXPECT会静默失败或延迟报错 - Mock 调用要明确声明预期:
mockRepo.EXPECT().Get("alice").Return(&detail, nil).Times(1) - 别忘了把 mock 注入到被测对象里,比如
svc := NewUserService(mockRepo)
表驱动测试是默认写法,不是加分项
微服务里一个校验函数往往要覆盖空字符串、超长、特殊字符、中文、SQL 注入片段等十几种输入。硬写十几个 TestCheckUsername_XXX 函数,维护成本高、易遗漏。
标准写法是用结构体切片组织用例:
tests := []struct {
input string
want bool
}{
{"alice", true},
{"ab", false},
{"", false},
{"a" + strings.Repeat("b", 256), false},
}
for _, tt := range tests {
t.Run(fmt.Sprintf("input=%q", tt.input), func(t *testing.T) {
if got := checkUsername(tt.input); got != tt.want {
t.Errorf("checkUsername(%q) = %v, want %v", tt.input, got, tt.want)
}
})
}
注意:t.Run 的 name 字符串里别拼接复杂表达式,否则失败时日志难以定位;tt.input 必须是简单值,避免闭包捕获问题。
别在单元测试里碰 Docker、DB 或网络
有人用 dockertest 启 MySQL 容器做“单元测试”,这其实是集成测试——它慢(秒级)、不稳定(端口冲突、镜像拉取失败)、无法并行(DB 共享状态)。这类测试应该单独放在 integration_test.go 文件里,用 go test -run Integration 显式触发。
真正的单元测试必须满足:
- 单次执行在毫秒级
- 不依赖任何外部进程或配置文件
- 可安全并行运行(
t.Parallel()) - 失败时能 100% 复现,不随环境变化
如果某个测试必须连 Redis 才过,说明它还没进入“单元”范畴——先抽接口、再 Mock、再验证逻辑,才是正路。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










