为什么Go测试并不可怕,而是必不可少
\n\n当你第一次用Go编写代码时,似乎编译器能捕获几乎所有问题:类型严格,错误明确,nil也在掌控之中。但是,当你的项目增长到500行以上时,你就会明白:没有测试是无法生存的。在一个地方修改代码会破坏另一个地方,而你只有在生产环境中才会发现这个问题。
在本文中,我将向新手开发者展示如何开始Go测试。我们将了解单元测试、表格驱动测试、模拟和代码覆盖率。没有废话——只有实用的示例和实践建议。
\n\n如果你已经使用Go编写代码至少几个月,这篇文章将为你节省数小时的调试时间。
\n\n开始之前需要了解的内容
\n\nGo提供了内置的testing包——无需额外安装任何东西。测试编写在以_test.go为后缀的文件中,并通过以下命令运行:
go test ./...\n\n基本规则:
\n- \n
- 测试函数必须命名为
TestXxx(t *testing.T),其中Xxx是被测试函数名,首字母大写。 \n - 测试文件与待测试代码放在同一目录下。 \n
- 错误通过
t.Error()或t.Fatal()报告。 \n
让我们直接进入实践。
\n\n编写你的第一个Go单元测试
\n\n假设我们有一个简单的函数,用于对两个数求和:
\n\n// calc.go\npackage calc\n\nfunc Sum(a, b int) int {\n return a + b\n}\n\n在同一文件夹中创建calc_test.go文件:
// calc_test.go\npackage calc\n\nimport "testing"\n\nfunc TestSum(t *testing.T) {\n result := Sum(2, 3)\n expected := 5\n\n if result != expected {\n t.Errorf("Sum(2,3) = %d; want %d", result, expected)\n }\n}\n\n运行测试:
\n\ngo test -v\n\n你会看到绿色的PASS——这是你的第一个测试胜利。请注意:我们使用了t.Errorf(非致命错误)——即使一个测试用例失败,测试也会继续运行。
表格驱动测试:当单个测试不方便时
\n\n想象一下,你需要用一个函数测试十组不同的输入数据。要写10个单独的测试吗?不,在Go中,我们有表格驱动测试(table-driven tests)。这是Go社区的标准做法。
\n\n在我们的calc.go中添加一个除法函数:
func Divide(a, b int) (int, error) {\n if b == 0 {\n return 0, errors.New("division by zero")\n }\n return a / b, nil\n}\n\n现在编写一个表格驱动测试:
\n\nfunc TestDivide(t *testing.T) {\n tests := []struct {\n name string\n a, b int\n expected int\n wantErr bool\n }{\n {"positive", 10, 2, 5, false},\n {"negative", -6, 3, -2, false},\n {"zero division", 5, 0, 0, true},\n {"same numbers", 7, 7, 1, false},\n }\n\n for _, tt := range tests {\n t.Run(tt.name, func(t *testing.T) {\n got, err := Divide(tt.a, tt.b)\n if tt.wantErr {\n if err == nil {\n t.Error("expected error, got nil")\n }\n return\n }\n if err != nil {\n t.Errorf("unexpected error: %v", err)\n }\n if got != tt.expected {\n t.Errorf("Divide(%d,%d) = %d; want %d", tt.a, tt.b, got, tt.expected)\n }\n })\n }\n}\n\n这里有什么好处:
\n- \n
- 每个测试用例都是一个带有名称的独立子测试。如果一个失败,其余的仍会执行。 \n
- 添加新用例很容易——只需在结构体中添加一行。 \n
- 代码读起来就像一份规范说明。 \n
使用-v标志运行——你将看到所有子测试的名称。这在调试时非常方便。