Go 包含一个testing包,你可以用来为代码编写自动化测试,还有一个go test命令,你可以用来运行这些测试。

本章要点

■  自动化测试使用一组特定的输入运行代码,并寻找特定的结果。如果代码输出与期望值匹配,则测试“通过”;否则,测试“失败”。
■   go test 工具用于运行测试。它在指定的包中查找文件名以\_test.go 结尾的文件。
■  你不需要将测试与正在测试的代码放在同一个包中,但是这样做将允许你访问该包中未导出的类型或函数。
■  测试需要使用 testing 包中的类型,所以你需要在每个测试文件的顶部导入该包。

■  一个\_test.go 文件可以包含一个或多个测试函数,它们的名字以 Test 开头。名字的其余部分可以是你想要的任何内容。
■  测试函数必须接受单个参数:一个指向 testing.T 值的指针。
■  测试代码可以对包中的函数和方法进行普通调用,然后检查返回值是否与预期值匹配。如果不匹配,测试失败。
■  你可以通过对 testing.T 值调用方法(例如 Error)来报告测试失败。大多数方法都接受一个字符串,其中包含解释测试失败原因的信息。
■   Errorf 方法的工作原理类似于 Error,但是它接受格式化字符串,就像 fmt.Printf 一样。
■  \_test.go 文件中名字不以 Test 开头的函数不会由 go test 运行。它们可以被测试用作“helper”函数。
■  表驱动测试是处理输入和预期输出的“表”的测试。它们将每一组输入传递给正在测试的代码,并检查代码的输出是否与预期值匹配。

编写最简单的测试

如上图的项目目录:

add.go 文件内容为:

package main

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

如果需要为add.go文件编写测试,只需创建一个 add_test.go 文件即可。文件内容为:

func TestAdd(t *testing.T) {
	if ans := Add(1, 2); ans != 3 {
		t.Error("测试失败")
	}

	if ans := Add(-10, -20); ans != -30 {
		t.Error("测试失败")
	}
}

然后项目目录执行go test 进行测试:

apple@192 chapter14_testing % go test
PASS
ok      headfirstgo/testing     1.543s

注意:
go test 会执行以Test开头的函数测试案例,如TestAdd

格式化输出详细的测试消息

func TestAdd(t *testing.T) {
	if ans := Add(1, 2); ans != 3 {
		t.Errorf("1 + 2 expected be 3, but %d got", ans)
	}

	if ans := Add(-10, -20); ans != -30 {
		t.Errorf("-10 + -20 expected be -30, but %d got", ans)
	}
}

与 Error 不同,Errorf 接受一个带格式化动词的字符串,就像 fmt.Printf 和 fmt.Sprintf 函数一样。

你可以使用 Errorf 在测试的失败消息中包含其他信息,例如传递给函数的参数、得到的返回值和期望的值。

加入我们修改 Add 函数,估计制造一个 bug:

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

此时,再执行 go test,则结果如下:

apple@192 chapter14_testing % go test
--- FAIL: TestAdd (0.00s)
    add_test.go:7: 1 + 2 expected be 3, but 4 got
    add_test.go:11: -10 + -20 expected be -30, but -29 got
FAIL
exit status 1
FAIL    headfirstgo/testing     0.578s

go test 选项

go test -v,-v 参数会显示每个用例的测试结果
go test -run,-run 指定部分或全部函数名,这样就只运行名称与指定的名称匹配的测试函数。

修改 add_test.go 文件内容

package main

import "testing"

func TestAddPositive(t *testing.T) {
	if ans := Add(1, 2); ans != 3 {
		t.Errorf("1 + 2 expected be 3, but %d got", ans)
	}
}

func TestAddNegative(t *testing.T) {
	if ans := Add(-10, -20); ans != -30 {
		t.Errorf("-10 + -20 expected be -30, but %d got", ans)
	}
}

执行 go test -v 结果如下:


可以看到,执行了TestAddPositiveTestAddNegative 两个测试案例,并输出了详细信息。

如果只想执行 TestAddPositive 呢?

执行 go test -v -run "Pos" 即可,测试输出如下:

apple@192 chapter14_testing % go test -v -run "Pos"
=== RUN   TestAddPositive
    add_test.go:7: 1 + 2 expected be 3, but 4 got
--- FAIL: TestAddPositive (0.00s)
FAIL
exit status 1
FAIL    headfirstgo/testing     0.719s

帮助函数(helpers)

对一些重复的逻辑,抽取出来作为公共的帮助函数(helpers),可以增加测试代码的可读性和可维护性。

在上述 TestAddPositiveTestAddNegative 测试案例中,会重复使用t.Errorf 语句,我们可以创建一个帮助函数。

func errorString(arg1 int, arg2 int, exp int, res int) string {
	return fmt.Sprintf("%d +  %d expected be %d, but %d got", arg1, arg2, exp, res)
}

func TestAddPositive(t *testing.T) {
	if res := Add(1, 2); res != 3 {
		t.Error(errorString(1, 2, 3, res))
	}
}

func TestAddNegative(t *testing.T) {
	if res := Add(-10, -20); res != -30 {
		t.Error(errorString(-10, -20, -30, res))
	}
}

当然,if 语句还有重复?还可以进一步封装即可。

IDEA自动生成的测试

我们看看IDEA自动为我们生成的测试案例:

func TestAdd(t *testing.T) {
	type args struct {
		a int
		b int
	}
	tests := []struct {
		name string
		args args
		want int
	}{
		// =========== 自己实现部分 start ========== 
		{
			name: "pos",
			args: args{a: 10, b: 20},
			want: 30,
		},
		{
			name: "neg",
			args: args{a: -10, b: -20},
			want: -30,
		},
		// =========== 自己实现部分 end ========== 
	}
	
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := Add(tt.args.a, tt.args.b); got != tt.want {
				t.Errorf("Add() = %v, want %v", got, tt.want)
			}
		})
	}
}

测试输出如下:

apple@192 chapter14_testing % go test -v
=== RUN   TestAdd
=== RUN   TestAdd/pos
    add_test.go:59: Add() = 31, want 30
=== RUN   TestAdd/neg
    add_test.go:59: Add() = -29, want -30
--- FAIL: TestAdd (0.00s)
    --- FAIL: TestAdd/pos (0.00s)
    --- FAIL: TestAdd/neg (0.00s)
FAIL
exit status 1
FAIL    headfirstgo/testing     0.369s