Go测试教程展示了如何使用内置的测试包在Golang中进行测试。
$ go version go version go1.18.1 linux/amd64
我们使用Go版本1.18。
单元测试是一个软件测试分支,测试软件的各个部分。单元测试的目的是验证软件的每个单元是否按设计执行。单元是任何软件中最小的可测试部分。单元测试不同于集成测试,后者将软件应用程序的不同单元和模块作为一个整体进行测试。
Go包含一个内置包testing用于进行测试。测试被写入以_test.go结尾的文件中。函数名的形式为
func TestXxx(*testing.T)
其中Xxx是要测试的函数的名称。
测试是通过gotest命令开始的。该命令编译程序和测试源并运行测试二进制文件。Gotest查找名称与文件模式*_test.go匹配的文件。最后会显示测试运行摘要。
Go测试文件可以包含测试函数、基准函数、模糊测试和示例函数。
Gotest可以在两种模式下运行:a)本地目录模式或b)包列表模式。当我们运行gotest时启用本地目录模式包参数。在这种模式下,gotest编译在当前目录中找到的包源和测试,然后运行生成的测试二进制文件。在此模式下缓存被禁用。
包列表模式在使用显式包名称运行命令gotest时启用;例如,gotest.、gotest./...(目录树中的所有包)或gotestutils。在此模式下,gotest编译并测试命令行中列出的每个包。此外,它缓存成功的包测试结果,以避免不必要的重复运行测试
gotest-v,其中-v标志代表详细,打印出所有已执行的测试函数的名称及其执行时间。使用-coverage选项运行测试代码覆盖率。我们可以使用-run选项运行特定测试,我们在其中应用正则表达式定位函数名称。
进行简单测试
在第一个例子中,我们测试了两个简单的函数。
package main
func hello() string {
return "Hello there!"
}
func morning() string {
return "Good morning!"
}
这两个函数返回短文本消息。
package main
import "testing"
func TestHello(t *testing.T) {
got := hello()
want := "Hello there!"
if got != want {
t.Errorf("got %s, want %s", got, want)
}
}
func TestMorning(t *testing.T) {
got := morning()
want := "Good morning!"
if got != want {
t.Errorf("got %s, want %s", got, want)
}
}
文件的名称是message_test.go。
import "testing"
testing包已导入。
func TestHello(t *testing.T) {
got := hello()
want := "Hello there!"
if got != want {
t.Errorf("got %s, want %s", got, want)
}
}
被测试的函数前面有Test关键字。如果预期值和返回值不同,我们会写一条错误消息。
$ go test PASS ok com.zetcode/first 0.001s
我们使用gotest命令运行测试。由于我们没有指定任何包名称,该工具会在当前工作目录中查找_test.go文件。通过发现的文件,它会查找具有TestXxx(*testing.T)签名的函数。
$ go test -v === RUN TestHello --- PASS: TestHello (0.00s) === RUN TestMorning --- PASS: TestMorning (0.00s) PASS ok com.zetcode/first 0.001s
要获取更多信息,我们使用-v选项。
$ go test -v -run Hello === RUN TestHello --- PASS: TestHello (0.00s) PASS ok com.zetcode/first 0.001s $ go test -v -run Mor === RUN TestMorning --- PASS: TestMorning (0.00s) PASS ok com.zetcode/first 0.001s
我们通过将正则表达式模式传递给-run选项来运行特定功能。
去测试算术函数
在下一个例子中,我们将测试四个算术函数。
package main
func Add(x int, y int) int {
return x + y
}
func Sub(x int, y int) int {
return x - y
}
func Div(x float64, y float64) float64 {
return x / y
}
func Mul(x int, y int) int {
return x * y
}
我们有加法、减法、除法和乘法函数。
package main
import "testing"
func TestAdd(t *testing.T) {
x, y := 2, 3
want := 5
got := Add(x, y)
if got != want {
t.Errorf("got %d, want %d", got, want)
}
}
func TestSub(t *testing.T) {
x, y := 5, 3
want := 2
got := Sub(x, y)
if got != want {
t.Errorf("got %d, want %d", got, want)
}
}
func TestDiv(t *testing.T) {
x, y := 7., 2.
want := 3.5
got := Div(x, y)
if got != want {
t.Errorf("got %f, want %f", got, want)
}
}
func TestMul(t *testing.T) {
x, y := 6, 5
want := 30
got := Mul(x, y)
if got != want {
t.Errorf("got %d, want %d", got, want)
}
}
在每个函数中,我们提供测试值和预期输出。
$ go test -v === RUN TestAdd --- PASS: TestAdd (0.00s) === RUN TestSub --- PASS: TestSub (0.00s) === RUN TestDiv --- PASS: TestDiv (0.00s) === RUN TestMul --- PASS: TestMul (0.00s) PASS ok com.zetcode/math 0.001s
我们已经通过了所有四项测试。
$ go test -cover PASS coverage: 100.0% of statements ok com.zetcode/math 0.001s
-cover选项提供有关测试覆盖了多少函数的信息。
$ go test -v -run "TestSub|TestMul" === RUN TestSub --- PASS: TestSub (0.00s) === RUN TestMul --- PASS: TestMul (0.00s) PASS ok com.zetcode/math 0.002s
使用管道运算符,我们选择两个特定的测试函数来运行。
Go表驱动测试
对于表驱动测试,我们有一个包含不同值和结果的表。测试工具迭代这些值并将它们传递给测试代码。这样我们就可以测试几种输入组合及其各自的输出。
这在其他语言中也称为参数化测试。
package main
type Val interface {
int | float64
}
func Add[T Val](x T, y T) T {
return x + y
}
func Sub[T Val](x T, y T) T {
return x - y
}
func Div[T Val](x T, y T) T {
return x / y
}
func Mul[T Val](x T, y T) T {
return x * y
}
使用泛型;我们可以传递整数和浮点值作为参数。
package main
import "testing"
type TestCase[T Val] struct {
arg1 T
arg2 T
want T
}
func TestAdd(t *testing.T) {
cases := []TestCase[int]{
{2, 3, 5},
{5, 5, 10},
{-7, 6, -1},
}
for _, tc := range cases {
got := Add(tc.arg1, tc.arg2)
if tc.want != got {
t.Errorf("Expected '%d', but got '%d'", tc.want, got)
}
}
}
func TestSub(t *testing.T) {
cases := []TestCase[int]{
{2, 3, -1},
{5, 5, 0},
{-7, -3, -4},
}
for _, tc := range cases {
got := Sub(tc.arg1, tc.arg2)
if tc.want != got {
t.Errorf("Expected '%d', but got '%d'", tc.want, got)
}
}
}
func TestDiv(t *testing.T) {
cases := []TestCase[int]{
{6., 3., 2.},
{5., 5., 1.},
{-10., 2., -5.},
}
for _, tc := range cases {
got := Div(tc.arg1, tc.arg2)
if tc.want != got {
t.Errorf("Expected '%d', but got '%d'", tc.want, got)
}
}
}
func TestMul(t *testing.T) {
cases := []TestCase[int]{
{7, 3, 21},
{5, 5, 25},
{-1, 6, -6},
}
for _, tc := range cases {
got := Mul(tc.arg1, tc.arg2)
if tc.want != got {
t.Errorf("Expected '%d', but got '%d'", tc.want, got)
}
}
}
我们的测试现在每个都有三个测试用例。
type TestCase[T Val] struct {
arg1 T
arg2 T
want T
}
我们创建一个TestCase类型,其中包含输入值和预期输出的字段。
func TestAdd(t *testing.T) {
cases := []TestCase[int]{
{2, 3, 5},
{5, 5, 10},
{-7, 6, -1},
}
for _, tc := range cases {
got := Add(tc.arg1, tc.arg2)
if tc.want != got {
t.Errorf("Expected '%d', but got '%d'", tc.want, got)
}
}
}
我们有一个包含三个测试用例的片段。我们遍历切片并为每种情况调用测试函数。
$ go test -v === RUN TestAdd --- PASS: TestAdd (0.00s) === RUN TestSub --- PASS: TestSub (0.00s) === RUN TestDiv --- PASS: TestDiv (0.00s) === RUN TestMul --- PASS: TestMul (0.00s) PASS ok com.zetcode/tables 0.001s
Go测试示例函数
可以添加用于运行一些基本测试和文档的示例函数。示例测试函数以示例词开头。
func ExampleHello() {
fmt.Println("hello")
// Output: hello
}
运行函数并将输出与输出字后面的值进行比较。
package main
func Add(x int, y int) int {
return x + y
}
我们有一个简单的Add函数。
package main
import (
"fmt"
"testing"
)
func TestAdd(t *testing.T) {
x, y := 2, 3
want := 5
got := Add(x, y)
if got != want {
t.Errorf("got %d, want %d", got, want)
}
}
func ExampleAdd() {
fmt.Println(Add(10, 6))
// Output: 16
}
测试文件包含TestAdd函数和ExampleAdd示例测试函数。
$ go test -v === RUN TestAdd --- PASS: TestAdd (0.00s) === RUN ExampleAdd --- PASS: ExampleAdd (0.00s) PASS ok com.zetcode/example 0.002s
去httptest
httptest包包含用于测试HTTP流量的实用程序。
ResponseRecorder是http.ResponseWriter的一个实现,它记录它的变化以供以后在测试中检查。
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
http.HandleFunc("/", HelloHandler)
log.Println("Listening...")
log.Fatal(http.ListenAndServe(":8080", nil))
}
func HelloHandler(w http.ResponseWriter, _ *http.Request) {
fmt.Fprintf(w, "Hello, there\n")
}
我们有一个带有一个HelloHandler的简单HTTP服务器。
package main
import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func TestHelloHandler(t *testing.T) {
want := "Hello there!"
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
fmt.Fprintln(w, want)
}))
defer ts.Close()
client := ts.Client()
res, err := client.Get(ts.URL)
if err != nil {
t.Errorf("expected nil got %v", err)
}
data, err := io.ReadAll(res.Body)
res.Body.Close()
if err != nil {
t.Errorf("expected nil got %v", err)
}
got := strings.TrimSpace(string(data))
if string(got) != want {
t.Errorf("got %s, want %s", got, want)
}
}
在TestHelloHandler中,我们使用httptest.NewServer启动测试服务器,并为HelloHandler实现一个相同的处理程序。客户端生成请求,并将响应与预期输出进行比较。在函数结束时,服务器关闭。
在本教程中,我们使用内置的测试模块在Go中执行了测试。
列出所有Go教程。
