
想象一下,你写了一个超级牛逼的Go程序,上线后却因为一个小小bug崩溃了。这感觉就像你精心准备了浪漫晚餐,结果发现忘记买主食——尴尬又致命!
测试就是你的安全网,让你在代码高空走钢丝时不怕摔死。而且Go的测试工具简单到令人发指,不学都对不起自己。
在Go中,测试不是玄学,而是有一套简单规则的约定:
测试文件:必须是
_test.go结尾。比如你的代码在
math.go,测试文件就应该是
math_test.go。这规矩比男朋友记得纪念日还简单!测试函数:必须用
Test开头,后面跟首字母大写的函数名。参数必须是
t *testing.T。例如:
// 在math_test.go中
func TestAdd(t *testing.T) {
result := Add(1, 2)
if result != 3 {
t.Errorf("期望3,得到%d", result)
}
}
是时候亮出我们的魔法咒语了:
go test
就这样?就这样!不加任何参数时,它会测试当前包,输出类似这样的结果:
ok your/package 0.5s
想看看详细过程?加个
-v参数:
go test -v
输出会变得话痨起来:
=== RUN TestAdd
--- PASS: TestAdd (0.00s)
=== RUN TestSubtract
--- PASS: TestSubtract (0.00s)
PASS
ok your/package 0.5s
如果你发现自己为同一个函数写了很多类似测试,像老妈子一样唠叨,那么表驱动测试就是你的救星!
表驱动测试就是把所有测试用例塞进一个"表格"里,然后用一个循环遍历执行。这概念比网红减肥食谱还简单:
func TestAddTableDriven(t *testing.T) {
// 定义测试用例表格
tests := []struct {
name string
a int
b int
expected int
}{
{"正数相加", 2, 3, 5},
{"零相加", 0, 0, 0},
{"负数相加", -1, -1, -2},
{"正负抵消", 5, -5, 0},
}
// 遍历执行每个测试用例
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := Add(tt.a, tt.b)
if result != tt.expected {
t.Errorf("Add(%d, %d) = %d; 期望 %d", tt.a, tt.b, result, tt.expected)
}
})
}
}
这种写法的好处多得数不清:
添加用例像在购物车加商品一样简单测试报告清晰得像高清无码,哪个失败了一眼就知道代码重复少得像程序员的头发有些测试像老年观光团,慢得让人想哭(比如数据库操作、网络请求)。好在Go提供了
-short标志,让你在开发时跳过这些磨叽的测试:
func TestSuperSlowDatabaseOperation(t *testing.T) {
if testing.Short() {
t.Skip("跳过耗时数据库测试")
}
// ... 下面是慢如蜗牛的测试代码
}
这样,当你执行
go test -short时,这些耗时测试就会被优雅地跳过,让你的测试速度快如闪电!
性能测试就像是给你代码办的运动会,看看它在压力下能跑多快。Go中称为基准测试(Benchmark)。
基准测试函数要以
Benchmark开头,接受
*testing.B参数:
func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
Add(1, 2)
}
}
运行基准测试:
go test -bench=.
输出会告诉你代码的性能:
BenchmarkAdd-16 1000000000 0.245 ns/op
这表示
Add函数平均每次操作耗时0.245纳秒——比眨眼还快!
性能测试实战:比较字符串拼接的三种方式
字符串拼接是日常开发中的常见操作,不同方法性能差异很大:
// 传统+
func Plus(n int, str string) string {
s := ""
for i := 0; i < n; i++ {
s += str
}
return s
}
// strings.Builder
func StrBuilder(n int, str string) string {
var builder strings.Builder
for i := 0; i < n; i++ {
builder.WriteString(str)
}
return builder.String()
}
基准测试结果会清楚地显示,
strings.Builder性能远超
+操作符,特别是在大量拼接时。所以记住:在循环内拼接字符串,请用strings.Builder!
Web开发中,经常需要测试HTTP接口。Go提供了
net/http/httptest包,让你的HTTP测试像点外卖一样简单!
假设我们有一个登录接口:
func TestUserHandler_Login(t *testing.T) {
// 创建测试引擎
r := gin.Default()
r.POST("/login", LoginHandler)
// 测试用例
tests := []struct {
name string
reqBody string
wantCode int
wantBody string
}{
{
name: "登录成功",
reqBody: `{"email": "123@qq.com", "pwd": "hello#world123"}`,
wantCode: http.StatusOK,
wantBody: `{"msg": "登录成功!"}`,
},
{
name: "参数不正确",
reqBody: `{"email": "123@qq.com", "pwd": "hello#world123",}`,
wantCode: http.StatusBadRequest,
wantBody: `{"msg": "参数不正确!"}`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 创建请求
req := httptest.NewRequest("POST", "/login", bytes.NewBufferString(tt.reqBody))
req.Header.Set("Content-Type", "application/json")
// 创建响应记录器
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
// 检查响应
if w.Code != tt.wantCode {
t.Errorf("状态码错误,得到%d,期望%d", w.Code, tt.wantCode)
}
if w.Body.String() != tt.wantBody {
t.Errorf("响应体错误,得到%s,期望%s", w.Body.String(), tt.wantBody)
}
})
}
}
测试覆盖率就像你的代码"体检报告",告诉你哪些代码被测试了,哪些还是"处女地"。
查看测试覆盖率:
go test -cover
输出会告诉你覆盖率百分比:
PASS
coverage: 85.7% of statements
ok your/package 0.5s
想生成详细的覆盖率报告?
go test -coverprofile=coverage.out
go tool cover -html=coverage.out -o coverage.html
这样会生成一个漂亮的HTML报告,用浏览器打开,你会看到哪些代码被测试覆盖(绿色),哪些没有(红色)。
Go测试还支持子测试和并发测试,让你的测试更加灵活:
func TestMyFunction(t *testing.T) {
// 子测试
t.Run("子测试A", func(t *testing.T) {
// 测试逻辑
})
t.Run("子测试B", func(t *testing.T) {
// 测试逻辑
})
// 并发测试
t.Run("并发测试", func(t *testing.T) {
t.Parallel() // 标记为可并行执行
// 测试逻辑
})
}
让我们用一个完整示例结束今天的学习。假设我们有一个数学工具包:
math.go:
package math
// Add 两个整数相加
func Add(a, b int) int {
return a + b
}
// Divide 两个整数相除
func Divide(a, b int) (int, error) {
if b == 0 {
return 0, fmt.Errorf("除数不能为零")
}
return a / b, nil
}
// IsPrime 检查是否为质数
func IsPrime(n int) bool {
if n < 2 {
return false
}
for i := 2; i*i <= n; i++ {
if n%i == 0 {
return false
}
}
return true
}
math_test.go:
package math
import (
"testing"
)
func TestAdd(t *testing.T) {
tests := []struct {
name string
a, b int
want int
}{
{"正数", 2, 3, 5},
{"零", 0, 0, 0},
{"负数", -1, -1, -2},
{"正负", 5, -3, 2},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := Add(tt.a, tt.b); got != tt.want {
t.Errorf("Add() = %v, 期望 %v", got, tt.want)
}
})
}
}
func TestDivide(t *testing.T) {
tests := []struct {
name string
a, b int
want int
wantErr bool
}{
{"正常除法", 6, 2, 3, false},
{"除零", 6, 0, 0, true},
{"小数截断", 7, 2, 3, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Divide(tt.a, tt.b)
if (err != nil) != tt.wantErr {
t.Errorf("Divide() 错误 = %v, 期望错误 %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("Divide() = %v, 期望 %v", got, tt.want)
}
})
}
}
func BenchmarkIsPrime(b *testing.B) {
for i := 0; i < b.N; i++ {
IsPrime(997) // 测试一个较大的质数
}
}
func ExampleAdd() {
sum := Add(3, 4)
fmt.Println(sum)
// Output: 7
}
运行所有测试并查看覆盖率:
go test -v -cover -bench=.
恭喜你!现在已经从测试菜鸟升级为Go测试玩家了!记住:
写测试不是浪费时间,而是节省未来调试的时间表驱动测试让你的用例组织得井井有条基准测试确保你的代码性能无忧HTTP测试让Web开发更加可靠测试就像给你的代码买了保险——平时觉得多余,出事时感激涕零。现在就去给你的Go代码加上测试吧,让它坚如磐石!
记住:真正的程序员不只是让代码能跑,而是确保代码一直能跑!