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
结果如下:
可以看到,执行了TestAddPositive
和 TestAddNegative
两个测试案例,并输出了详细信息。
如果只想执行 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),可以增加测试代码的可读性和可维护性。
在上述 TestAddPositive
和 TestAddNegative
测试案例中,会重复使用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