从零开始学习Go测试:新手开发者指南

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

为什么Go测试并不可怕,而是必不可少

\n\n

当你第一次用Go编写代码时,似乎编译器能捕获几乎所有问题:类型严格,错误明确,nil也在掌控之中。但是,当你的项目增长到500行以上时,你就会明白:没有测试是无法生存的。在一个地方修改代码会破坏另一个地方,而你只有在生产环境中才会发现这个问题。

\n\n

在本文中,我将向新手开发者展示如何开始Go测试。我们将了解单元测试、表格驱动测试、模拟和代码覆盖率。没有废话——只有实用的示例和实践建议。

\n\n

如果你已经使用Go编写代码至少几个月,这篇文章将为你节省数小时的调试时间。

\n\n

开始之前需要了解的内容

\n\n

Go提供了内置的testing包——无需额外安装任何东西。测试编写在以_test.go为后缀的文件中,并通过以下命令运行:

\n\n
go test ./...
\n\n

基本规则:

\n
    \n
  • 测试函数必须命名为TestXxx(t *testing.T),其中Xxx是被测试函数名,首字母大写。
  • \n
  • 测试文件与待测试代码放在同一目录下。
  • \n
  • 错误通过t.Error()或t.Fatal()报告。
  • \n
\n\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文件:

\n\n
// 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\n
go test -v
\n\n

你会看到绿色的PASS——这是你的第一个测试胜利。请注意:我们使用了t.Errorf(非致命错误)——即使一个测试用例失败,测试也会继续运行。

\n\n

表格驱动测试:当单个测试不方便时

\n\n

想象一下,你需要用一个函数测试十组不同的输入数据。要写10个单独的测试吗?不,在Go中,我们有表格驱动测试(table-driven tests)。这是Go社区的标准做法。

\n\n

在我们的calc.go中添加一个除法函数:

\n\n
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\n
func 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
\n\n

使用-v标志运行——你将看到所有子测试的名称。这在调试时非常方便。

\n\n

测试

博客

书籍推荐