Go 单元测试
单元测试是软件开发中的一个重要环节,它用于验证代码的各个独立单元(通常是函数或方法)是否按预期工作。在Go语言中,单元测试是通过内置的testing
包来实现的。本文将带你从零开始学习如何编写Go单元测试。
什么是单元测试?
单元测试是一种自动化测试方法,用于验证代码的最小可测试单元(通常是函数或方法)是否按预期工作。通过编写单元测试,开发者可以在代码变更时快速发现潜在的问题,从而提高代码的可靠性和可维护性。
在Go中,单元测试通常与待测试的代码放在同一个包中,并以_test.go
结尾的文件中编写。
编写你的第一个单元测试
让我们从一个简单的例子开始。假设我们有一个函数Add
,用于将两个整数相加:
// math.go
package math
func Add(a, b int) int {
return a + b
}
为了测试这个函数,我们可以编写一个测试文件math_test.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
会输出一个错误信息。
运行单元测试
要运行这个测试,可以在终端中执行以下命令:
go test
如果测试通过,你会看到类似以下的输出:
PASS
ok your/package/path 0.001s
如果测试失败,输出会显示具体的错误信息。
测试表(Table-Driven Tests)
在实际开发中,我们通常需要对同一个函数进行多次测试,以覆盖不同的输入情况。这时,可以使用测试表来简化测试代码。
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语言提供了内置的工具来检查测试覆盖率。你可以通过以下命令生成测试覆盖率报告:
go test -cover
输出会显示当前测试的覆盖率百分比。你还可以生成一个详细的HTML报告:
go test -coverprofile=coverage.out
go tool cover -html=coverage.out
实际案例:测试HTTP处理函数
在实际开发中,我们经常需要测试HTTP处理函数。假设我们有一个简单的HTTP处理函数:
// handler.go
package main
import (
"net/http"
"io"
)
func HelloHandler(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "Hello, World!")
}
我们可以使用net/http/httptest
包来测试这个处理函数:
// 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
包、测试表、测试覆盖率工具等。
附加资源
练习
-
为以下函数编写单元测试:
gofunc Multiply(a, b int) int {
return a * b
} -
使用测试表来测试
Multiply
函数,覆盖正数、负数和零的情况。 -
编写一个HTTP处理函数,用于返回当前时间,并为其编写单元测试。
通过完成这些练习,你将更加熟悉Go单元测试的编写方法和技巧。