go测试文件须命名为_test.go且与源文件同目录同包;测试函数以Test开头并接收testing.T;用t.Error/t.Fatal断言,推荐表驱动测试和接口mock。

测试文件命名和位置怎么放才不会被忽略
Go 的 go test 命令只识别以 _test.go 结尾的文件,且必须和被测代码在同一个包(即同目录下)。如果放在 test/ 子目录或命名为 mytest.go,go test 直接跳过——不是报错,是静默忽略。
- 正确命名:
calculator_test.go(对应calculator.go) - 必须同包:不能加
package test,得写package main或package calculator,和源文件一致 - 测试函数必须以
Test开头,且接收单个*testing.T参数,例如func TestAdd(t *testing.T)
如何用 testing.T 写一个不 panic 的基础断言
Go 标准库不提供 assert.Equal 这类函数,所有判断靠 t.Error、t.Fatal 手动触发。关键区别在于:t.Error 记录错误但继续执行,t.Fatal 立即终止当前测试函数。
func TestAdd(t *testing.T) { result := Add(2, 3) if result != 5 { t.Errorf("Add(2, 3) = %d; want 5", result) // 推荐:带上下文 } }
- 别用
panic或log.Fatal:会绕过testing框架,导致覆盖率统计失败、无法并行运行 - 避免裸比较
if result != 5 { t.Fail() }:没有输出信息,排查时只能看源码猜 - 字符串插值建议用
%v而非%d,适配任意类型
表驱动测试怎么组织才不重复写 t.Run
对多个输入/输出组合,用切片定义测试用例,配合 t.Run 实现子测试隔离。每个子测试独立计时、可单独运行(如 go test -run=TestAdd/2+3),失败时也只报具体子项。
func TestAdd(t *testing.T) { tests := []struct { a, b, want int }{ {2, 3, 5}, {-1, 1, 0}, {0, 0, 0}, } for _, tt := range tests { t.Run(fmt.Sprintf("%d+%d", tt.a, tt.b), func(t *testing.T) { if got := Add(tt.a, tt.b); got != tt.want { t.Errorf("got %d, want %d", got, tt.want) } }) } }
- 子测试名必须唯一,否则后一个覆盖前一个;用
fmt.Sprintf构造比硬编码更安全 - 循环变量
tt必须在for内部声明(for _, tt := range tests),否则闭包会捕获最后一次迭代的值 - 不要在子测试里调
t.Parallel()除非确认函数无共享状态——初级项目通常没必要
mock 外部依赖时为什么不能直接 new 一个结构体
当函数依赖数据库、http 客户端等,需抽象为接口再替换实现。直接 new(http.Client) 仍会发起真实请求;而用接口 + mock,才能控制返回值、验证调用次数。
立即学习“go语言免费学习笔记(深入)”;
type Fetcher interface { Get(url string) (string, error) } func Download(f Fetcher, url string) (string, error) { return f.Get(url) } // 测试用 mock type mockFetcher struct{ called int } func (m *mockFetcher) Get(url string) (string, error) { m.called++ return "fake-body", nil }
- 接口定义要窄:只包含当前函数实际调用的方法,别把整个
http.Client方法都塞进去 - mock 结构体字段(如
called)用于验证行为,比如断言if m.called != 1 { t.Error("expected call once") } - 初级项目慎用第三方 mock 库(如 gomock):手写 mock 更轻量,也更容易理解依赖边界
测试最难的部分不是写断言,而是决定「该测什么」——比如边界值、错误路径、并发场景。很多初级项目只测 happy path,结果上线后遇到空指针或超时就崩,却没对应测试用例。