Go测试是内置轻量级机制,需同包、_test.go文件、TestXxx签名;表驱动测试用结构体切片统一管理用例,配合t.Run定位失败项;go test常用-v、-run、-coverprofile等参数提升效率。Go测试是内置在语言工具链里的轻量级验证机制,不是第三方库,也不需要配置文件或启动服务。它靠约定驱动:只要文件名以
_test.go 结尾、函数名以 Test 开头、参数是 *testing.T,go test 就能自动发现并执行。
最简路径就是三件事:同包、_test.go、TestXxx签名。别把测试文件扔到别的目录,也别用小写开头的函数名(比如 testAdd 不会被识别)。
package main 或 package utils 都行,但不能改成 package test
Test + 首字母大写的单词,例如 TestAdd 合法,testAdd 或 Testadd 无效"testing" 包,哪怕只用了一次 t.Error
t.Errorf 而不是 panic 或 log.Fatal,否则测试框架无法统计失败数package main
import "testing"
func Add(a, b int) int {
return a + b
}
func TestAdd(t *testing.T) {
got := Add(2, 3)
want := 5
if got != want {
t.Errorf("Add(2,3) = %d, want %d", got, want)
}
}
当你开始写 TestAddWithZero、TestAddWithNegative、TestAddOverflow……就该停手了。重复结构+相似逻辑=维护噩梦。表驱动把用例收进切片,一循环全跑完,加新 case 只改数据不改逻辑。
name 字段,出错时 t.Run(name, ...) 能准确定位哪条 case 挂了range tests 然后直接闭包引用 tc——所有子测试会共享最后一个值,要用 tc := tc 显式捕获func TestAddTableDriven(t *testing.T) {
tests := []struct {
name string
a, b int
expected int
}{
{"positive", 2, 3, 5},
{"zero", 0, 0, 0},
{"negative", -1, -1, -2},
}
for _, tc := range tests {
tc := tc // 防止闭包陷阱
t.Run(tc.name, func(t *testing.T) {
got := Add(tc.a, tc.b)
if got != tc.expected {
t.Errorf("got %d, want %d", got, tc.expected)
}
})
}
}
go test 默认静默运行,失败才报错。但开发中你几乎每次都要加参数:查哪条失败、看覆盖率、单跑某个 case、确认是否真慢……不用白不用。
go test -v:必加。显示每个 TestXxx 的执行时间和 PASS/FAIL,没它等于盲测go test -run ^TestAdd$:正则精确匹配,^ 和 $ 防止误中 TestAddWithZero
go test -coverprofile=cover.out && go tool
cover -html=cover.out:生成 HTML 覆盖率报告,一眼看出哪些 if 分支根本没走go test -timeout 5s:防止某个测试卡死,尤其涉及网络或 channel 等待的场景t.Fatal 或 t.Fatalf 会终止当前测试函数,但不会影响其他测试;而 t.Error 允许继续执行后续断言——这决定了你是想“一条失败全盘放弃”,还是“一次多验几个点”。选哪个,得看被测逻辑是否相互依赖。