纯逻辑函数应优先测试,需隔离外部依赖、用结构体切片定义表驱动数据、避免非确定操作;mock仅限i/o,慎mock纯函数;error判断要用errors.is,覆盖所有分支;禁用time.sleep,排查goroutine泄漏和race检测器。

如何为纯逻辑函数写可信赖的单元测试
Go 的单元测试不是“加个 TestXxx 函数就行”,关键在于隔离、可控、可重复。纯逻辑函数(比如计算折扣、解析时间字符串、校验手机号)最适合作为第一波测试目标,因为它们不依赖外部状态。
实操建议:
- 用
go test -v运行时带上-v,能看清每个测试用例的输入输出,快速定位哪一组input导致expected不匹配 - 测试数据优先用结构体切片定义,避免散落在多个
if里:tests := []struct { input string expected bool }{ {"13800138000", true}, {"abc", false}, } - 别在测试里调用
time.Now()或rand.Intn()—— 它们让测试非确定。改用参数注入或接口抽象,例如把时间依赖抽成clock NowFunc类型
mock 外部依赖时,什么该 mock、什么不该 mock
数据库、HTTP client、文件系统这些 I/O 操作必须 mock;但标准库里的 json.Marshal、strings.TrimSpace 这类纯函数,mock 反而增加维护成本且掩盖真实行为。
常见错误现象:
- 为
os.ReadFile写 mock,却漏掉对返回错误的覆盖(比如os.ErrNotExist),导致测试通过但线上 panic - 用
gomock生成 interface mock,但没实现所有方法,运行时报panic: method XXX is not expected - 过度 mock:给一个只调用一次
http.Get的函数,硬套gock或httptest.Server,不如直接用httptest.NewServer启一个最小响应服务
更轻量的做法是:对 HTTP 客户端,用 http.Client{Transport: <code>RoundTripFunc} 替换底层 transport;对 DB,用 sqlmock 拦截 Query/Exec 调用并返回预设结果。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
表驱动测试中,error 判断容易漏掉的细节
Go 习惯用 error 返回值表达失败,但很多测试只检查 err != nil,忽略具体 error 类型或 message,导致逻辑 bug 被掩盖。
实操建议:
- 用
errors.Is(err, yourpkg.ErrInvalidID)判断是否为自定义错误,而不是err.Error() == "xxx"—— 后者脆弱且不支持多语言提示 - 若函数可能返回多种 error(如
ErrNotFound/ErrPermissionDenied),测试 case 中必须显式覆盖每种分支 - 注意
nilerror 和nilstruct 指针的区别:比如函数返回(*User, error),测试里不能只断言user != nil就认为成功,得同时确认err == nil
go test 跑得慢?先查这三件事
本地 go test 执行耗时超过 2 秒,大概率不是代码问题,而是测试本身引入了隐式依赖。
排查路径:
- 是否有测试调用了
time.Sleep(100 * time.Millisecond)—— Go 测试里禁止 sleep,应改用 channel + timeout 或testify/assert.Eventually - 是否在
TestMain里启动了未关闭的 goroutine 或监听 socket(比如忘了srv.Close()) - 是否用了
go test ./... -race却没关掉 race detector?它会让测试变慢 5–10 倍,日常开发用go test即可,CI 阶段再开 race
真正影响稳定性的,往往不是覆盖率数字,而是那些没被触发的 error 分支、没被测到的边界输入、以及测试本身悄悄引入的时序耦合 —— 这些比 “有没有写测试” 更值得花时间盯住。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










