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 更轻量,也更容易理解依赖边界










