Go语言在语言层面内置了测试支持,通过testing包提供单元测试、基准测试、示例测试等功能。编写测试代码无需引入外部框架,只需遵循简单的命名规则(*_test.go文件,TestXxx函数名)。这种设计使得测试编写变得轻量且高效,鼓励开发者养成测试驱动的习惯。
go test 自动运行测试;go test -bench . 运行基准测试;go test -cover 查看测试覆盖率。
假设我们有一个math.go文件,包含待测试的函数:
package main
func Add(a, b int) int {
return a + b
}
func Divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
对应的测试文件math_test.go:
package main
import "testing"
func TestAdd(t *testing.T) {
result := Add(2, 3)
expected := 5
if result != expected {
t.Errorf("Add(2,3) = %d; want %d", result, expected)
}
}
func TestDivide(t *testing.T) {
result, err := Divide(10, 2)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result != 5 {
t.Errorf("Divide(10,2) = %d; want 5", result)
}
// 测试除零错误
_, err = Divide(5, 0)
if err == nil {
t.Error("expected error but got nil")
}
}
运行测试:go test 或 go test -v(显示详细输出)。
表驱动测试是Go中常见的测试模式,通过定义测试用例切片,循环执行并判断结果,极大减少重复代码。
func TestAddTableDriven(t *testing.T) {
tests := []struct {
name string
a, b int
want int
}{
{"positive", 1, 2, 3},
{"zero", 0, 0, 0},
{"negative", -1, -2, -3},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := Add(tt.a, tt.b)
if got != tt.want {
t.Errorf("Add(%d,%d) = %d, want %d", tt.a, tt.b, got, tt.want)
}
})
}
}
使用t.Run可以创建子测试,每个子测试可以独立运行,失败时只影响自身。支持通过go test -run TestXxx/subtest单独运行子测试。
func TestDivide(t *testing.T) {
t.Run("normal division", func(t *testing.T) {
got, err := Divide(10, 2)
if err != nil {
t.Fatal(err)
}
if got != 5 {
t.Errorf("got %d, want 5", got)
}
})
t.Run("division by zero", func(t *testing.T) {
_, err := Divide(5, 0)
if err == nil {
t.Error("expected error, got nil")
}
})
}
在辅助函数中调用t.Helper()可以报告错误时定位到调用者的行号,而非辅助函数内部。
func assertEqual(t *testing.T, got, want int) {
t.Helper()
if got != want {
t.Errorf("got %d, want %d", got, want)
}
}
func TestAddWithHelper(t *testing.T) {
assertEqual(t, Add(2,3), 5)
}
t.Skip可以跳过某些测试(如依赖外部环境)。t.Parallel可将测试标记为并行运行。
func TestDatabase(t *testing.T) {
if os.Getenv("DB_TEST") == "" {
t.Skip("跳过数据库测试,未设置 DB_TEST 环境变量")
}
// 数据库测试...
}
func TestParallel(t *testing.T) {
t.Parallel() // 此测试将与其他并行测试同时运行
// 测试逻辑
}
基准测试用于评估代码性能。函数名必须以Benchmark开头,参数类型为*testing.B。循环次数由b.N决定,测试框架会自动调整。
// 待测试函数(字符串拼接)
func ConcatSimple(s []string) string {
result := ""
for _, v := range s {
result += v
}
return result
}
func ConcatBuilder(s []string) string {
var builder strings.Builder
for _, v := range s {
builder.WriteString(v)
}
return builder.String()
}
// 基准测试函数
func BenchmarkConcatSimple(b *testing.B) {
strs := []string{"a", "b", "c", "d", "e"}
for i := 0; i < b.N; i++ {
ConcatSimple(strs)
}
}
func BenchmarkConcatBuilder(b *testing.B) {
strs := []string{"a", "b", "c", "d", "e"}
for i := 0; i < b.N; i++ {
ConcatBuilder(strs)
}
}
运行基准测试:go test -bench=. 或 go test -bench=Concat。结果示例:
-benchmem 显示内存分配统计(B/op, allocs/op)。-benchtime=5s 延长测试时间。-count=5 重复多次取平均值。go test -bench=Concat -benchmem -cpu=1,2,4
go test -cover 显示覆盖率百分比;go test -coverprofile=c.out 生成覆盖率文件;go tool cover -html=c.out 在浏览器中查看未覆盖的代码行。
示例测试用于文档中展示用法,并可验证输出是否与// Output:注释匹配。
func ExampleAdd() {
sum := Add(3, 5)
fmt.Println(sum)
// Output: 8
}
package main
// 递归版斐波那契
func FibRecursive(n int) int {
if n < 2 {
return n
}
return FibRecursive(n-1) + FibRecursive(n-2)
}
// 迭代版斐波那契
func FibIterative(n int) int {
if n < 2 {
return n
}
a, b := 0, 1
for i := 2; i <= n; i++ {
a, b = b, a+b
}
return b
}
// 测试函数
func TestFib(t *testing.T) {
tests := []struct{ n, want int }{
{0, 0}, {1, 1}, {2, 1}, {3, 2}, {10, 55},
}
for _, tt := range tests {
got := FibIterative(tt.n)
if got != tt.want {
t.Errorf("Fib(%d) = %d, want %d", tt.n, got, tt.want)
}
}
}
// 基准测试对比
func BenchmarkFibRecursive(b *testing.B) {
for i := 0; i < b.N; i++ {
FibRecursive(20)
}
}
func BenchmarkFibIterative(b *testing.B) {
for i := 0; i < b.N; i++ {
FibIterative(20)
}
}
运行go test -bench=Fib -benchmem可看到迭代版性能远超递归版。
_test.go文件,使用go test即可快速验证正确性和性能。表驱动测试和子测试是推荐的最佳实践。基准测试则帮助我们发现性能瓶颈,指导优化方向。持续编写测试是构建可靠Go服务的关键。