Go 语言中,单元测试和基准测试是保证代码质量和性能的重要工具。Go 标准库提供了 testing 包,结合简单的命名约定,让测试变得简洁高效。
// 待测试的函数(math.go)
func Add(a, b int) int {
return a + b
}
// 测试函数(math_test.go)
package main
import "testing"
func TestAdd(t *testing.T) {
//用(测试数据)表驱动测试
tests := []struct {
a, b int
want int
}{
{1, 2, 3},
{0, 0, 0},
{-1, 1, 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("Add(%d, %d) = %d, want %d", tt.a, tt.b, got, tt.want)
}
})
}
}
方法 | 用途 | 示例 |
---|---|---|
t.Error(args…) | 输出错误并继续测试 | if x != y { t.Error(“x != y”) } |
t.Fatal(args…) | 输出错误并终止测试 | if err != nil { t.Fatal(err) } |
t.Errorf(format, …) | 格式化输出错误 | t.Errorf(“got %d, want %d”, got, want) |
t.Run(name, fn) | 子测试(并行执行) | t.Run(“SubTest”, func(t *testing.T) { … }) |
// 待测试的函数(strings.go)
func Reverse(s string) string {
runes := []rune(s)
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
runes[i], runes[j] = runes[j], runes[i]
}
return string(runes)
}
// 基准测试函数(strings_test.go)
package main
import "testing"
func BenchmarkReverse(b *testing.B) {
s := "Hello, World!"
for i := 0; i < b.N; i++ {
Reverse(s)
}
}
执行 go test -bench=. 后,输出示例:
BenchmarkReverse-8 10000000 128 ns/op 48 B/op 2 allocs/op
func TestDivide(t *testing.T) {
tests := []struct {
name string
a, b float64
want float64
wantErr bool
}{
{"正常除法", 10, 2, 5, false},
{"除以零", 5, 0, 0, true},
{"负数", -10, 2, -5, 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.Fatalf("Divide() error = %v, wantErr %v", err, tt.wantErr)
}
if got != tt.want {
t.Errorf("Divide() = %v, want %v", got, tt.want)
}
})
}
}
func TestCalculator(t *testing.T) {
t.Run("加法", func(t *testing.T) {
if Add(1, 2) != 3 {
t.Error("加法失败")
}
})
t.Run("减法", func(t *testing.T) {
if Subtract(5, 3) != 2 {
t.Error("减法失败")
}
})
}
统计代码被测试覆盖的比例:
go test -coverprofile=coverage.out # 生成覆盖率报告
go tool cover -html=coverage.out # 以 HTML 形式查看
// 定义接口
type EmailSender interface {
Send(to, subject, body string) error
}
// 实现类
type UserService struct {
emailSender EmailSender
}
// 测试时注入模拟实现
func TestUserService_SendWelcomeEmail(t *testing.T) {
mockSender := &MockEmailSender{}
service := UserService{emailSender: mockSender}
// 执行测试逻辑...
}
go get github.com/golang/mock/[email protected]
go test -bench=. -benchmem # 显示内存分配信息
func BenchmarkConcurrentReverse(b *testing.B) {
s := "Hello, World!"
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
Reverse(s)
}
})
}
每个测试只验证一个明确的功能点。
使用描述性的测试名称(如 TestAdd_NegativeNumbers)。
避免测试私有函数,优先测试公共接口。
关注相对性能变化,而非绝对数值。
使用 -benchtime 调整测试时间:go test -bench=. -benchtime=5s。
避免在基准测试中包含初始化代码。
将测试文件与被测试代码放在同一包中。
使用 _test 子目录存放需要特殊权限或复杂设置的测试。
Go 的测试工具链简洁而强大,通过命名约定和标准库提供的 testing 包,开发者可以轻松编写单元测试和基准测试。合理运用表驱动测试、子测试和模拟技术,能够提高测试覆盖率和代码质量,同时通过基准测试持续优化性能。