跳到主要内容

Go 单元测试

单元测试是软件开发中的一个重要环节,它用于验证代码的各个独立单元(通常是函数或方法)是否按预期工作。在Go语言中,单元测试是通过内置的testing包来实现的。本文将带你从零开始学习如何编写Go单元测试。

什么是单元测试?

单元测试是一种自动化测试方法,用于验证代码的最小可测试单元(通常是函数或方法)是否按预期工作。通过编写单元测试,开发者可以在代码变更时快速发现潜在的问题,从而提高代码的可靠性和可维护性。

在Go中,单元测试通常与待测试的代码放在同一个包中,并以_test.go结尾的文件中编写。

编写你的第一个单元测试

让我们从一个简单的例子开始。假设我们有一个函数Add,用于将两个整数相加:

go
// math.go
package math

func Add(a, b int) int {
return a + b
}

为了测试这个函数,我们可以编写一个测试文件math_test.go

go
// math_test.go
package math

import "testing"

func TestAdd(t *testing.T) {
result := Add(2, 3)
if result != 5 {
t.Errorf("Add(2, 3) = %d; want 5", result)
}
}

在这个测试中,我们使用了testing包中的T类型来报告测试结果。如果Add函数的返回值不等于5,t.Errorf会输出一个错误信息。

运行单元测试

要运行这个测试,可以在终端中执行以下命令:

bash
go test

如果测试通过,你会看到类似以下的输出:

bash
PASS
ok your/package/path 0.001s

如果测试失败,输出会显示具体的错误信息。

测试表(Table-Driven Tests)

在实际开发中,我们通常需要对同一个函数进行多次测试,以覆盖不同的输入情况。这时,可以使用测试表来简化测试代码。

go
func TestAddTableDriven(t *testing.T) {
var tests = []struct {
a, b int
want int
}{
{1, 2, 3},
{0, 0, 0},
{-1, 1, 0},
{-1, -1, -2},
}

for _, tt := range tests {
testname := fmt.Sprintf("%d+%d", tt.a, tt.b)
t.Run(testname, func(t *testing.T) {
ans := Add(tt.a, tt.b)
if ans != tt.want {
t.Errorf("got %d, want %d", ans, tt.want)
}
})
}
}

在这个例子中,我们定义了一个结构体切片tests,每个元素包含输入参数和期望的输出。然后,我们使用t.Run为每个测试用例生成一个子测试。

测试覆盖率

Go语言提供了内置的工具来检查测试覆盖率。你可以通过以下命令生成测试覆盖率报告:

bash
go test -cover

输出会显示当前测试的覆盖率百分比。你还可以生成一个详细的HTML报告:

bash
go test -coverprofile=coverage.out
go tool cover -html=coverage.out

实际案例:测试HTTP处理函数

在实际开发中,我们经常需要测试HTTP处理函数。假设我们有一个简单的HTTP处理函数:

go
// handler.go
package main

import (
"net/http"
"io"
)

func HelloHandler(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "Hello, World!")
}

我们可以使用net/http/httptest包来测试这个处理函数:

go
// handler_test.go
package main

import (
"net/http"
"net/http/httptest"
"testing"
)

func TestHelloHandler(t *testing.T) {
req, err := http.NewRequest("GET", "/hello", nil)
if err != nil {
t.Fatal(err)
}

rr := httptest.NewRecorder()
handler := http.HandlerFunc(HelloHandler)

handler.ServeHTTP(rr, req)

if status := rr.Code; status != http.StatusOK {
t.Errorf("handler returned wrong status code: got %v want %v",
status, http.StatusOK)
}

expected := "Hello, World!"
if rr.Body.String() != expected {
t.Errorf("handler returned unexpected body: got %v want %v",
rr.Body.String(), expected)
}
}

在这个测试中,我们使用httptest.NewRecorder来捕获HTTP响应,并验证状态码和响应体是否符合预期。

总结

单元测试是确保代码质量的重要手段。通过编写单元测试,你可以快速发现代码中的问题,并在代码变更时保持信心。Go语言提供了强大的工具来支持单元测试,包括testing包、测试表、测试覆盖率工具等。

附加资源

练习

  1. 为以下函数编写单元测试:

    go
    func Multiply(a, b int) int {
    return a * b
    }
  2. 使用测试表来测试Multiply函数,覆盖正数、负数和零的情况。

  3. 编写一个HTTP处理函数,用于返回当前时间,并为其编写单元测试。

通过完成这些练习,你将更加熟悉Go单元测试的编写方法和技巧。