Go测试是内置轻量级机制,需同包、_test.go文件、TestXxx签名;表驱动测试用结构体切片统一管理用例,配合t.Run定位失败项;go test常用-v、-run、-coverprofile等参数提升效率。

_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)
}
}
为什么表驱动测试比一堆 TestXxx 更实用
当你开始写 TestAddWithZero、TestAddWithNegative、TestAddOverflow……就该停手了。重复结构+相似逻辑=维护噩梦。表驱动把用例收进切片,一循环全跑完,加新 case 只改数据不改逻辑。
- 每个测试项最好带
name字段,出错时t.Run(name, ...)能准确定位哪条 case 挂了 - 不要在循环里用
range tests然后直接闭包引用tc——所有子测试会共享最后一个值,要用tc := tc显式捕获 - 边界值(0、负数、最大值)、空输入、错误返回,都塞进表里,比靠记忆补漏靠谱得多
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 命令常用参数不是摆设,而是日常刚需
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 允许继续执行后续断言——这决定了你是想“一条失败全盘放弃”,还是“一次多验几个点”。选哪个,得看被测逻辑是否相互依赖。









