本次分享我们来了解 Go 中的测试如何编写。
在 Go 中,*_test.go
源码文件,是测试文件。仅在执行 go test
命令时,测试文件中的测试函数会被编译运行。
一般将对独立函数的测试代码与起源码置于同级目录下,例如:
play
├── fib.go
└── fib_test.go
需要整合多个模块的集成测试,设计一个 test 包,例如:
api/test
├── create_normal_user_test.go
├── create_admin_user_test.go
└── update_user_test.go
功能测试
测试一个独立函数的功能,最流行的测试模式是 Table Driven Tests 。
为了确定一个函数的功能实现是否符合预期,我们需要准备好一批的输入和期望输出。
将函数作为黑盒,我们可以将输入和输出类型定义在结构体中,通过循环执行测试用例。
从一个简单的例子开始
实现一个函数,计算第 n 个斐波那契数。
函数实现写在 fib.go
文件中:
package fib
func Fib(n int) int {
if n < 2 {
return n
}
return Fib(n-1) + Fib(n-2)
}
设计一个测试用的结构体,包含输入和预期输出:
type fibTest struct {
n int // input
expected int // expected result
}
测试函数写在 fib_test.go
中:
func TestFib(t *testing.T) {
var fibTests = []fibTest {
{1, 1},
{2, 1},
{3, 2},
{4, 3},
{5, 5},
{6, 8},
{7, 13},
}
for _, tt := range fibTests {
actual := Fib(tt.n)
if actual != tt.expected {
t.Errorf("Fib(%d): expected %d, actual %d", tt.n, tt.expected, actual)
}
}
}
运行效果 https://play.golang.org/p/U1krMF0dzE6
运行测试
go test .
如果测试函数过多,通过向 -run
参数传入正则来筛选需要执行的测试。
小结
Table Driven Tests 能够编写出简洁的单元测试,能够以很少的代码得到很大的测试覆盖率。只要设计得当,添加其他测试用例就像在测试表中添加一个新元素一样简单。
这也不是编写 Table Driven Tests 的唯一方式, Go 标准库中还有很多技巧值得学习,可以先从 math
包和 time
包的测试开始学。
基准测试
基准测试也是写在 *_test.go
文件中,需要遵循测试规则。比如我们测试一下计算第 10 个斐波那契数,fib_test.go
中添加以下函数:
func BenchmarkFib10(b *testing.B) {
// run the Fib function b.N times
for n := 0; n < b.N; n++ {
Fib(10)
}
}
编写基准测试的区别:
- 基准测试函数以
Benchmark
开头而不是Test
- 基准测试会被 testing 包运行许多次,
b.N
的值每次都会增加,直到基准测试结果稳定。 - 每个基准测试必须执行
b.N
次测试代码,像BenchmarkFib10
中使用 for 循环来测试 fib 函数。
运行基准测试
go test -bench .
-bench
需要接收一个正则,你可以利用它筛选需要执行的基准测试。
如果只需要运行基准测试,可以把测试函数屏蔽,传入一个没有匹配结果的正则即可 -run=XXX
许多输入的基准测试
原本 fib 函数是递归实现,它的运行时间也应是随输入值变大而指数增长。
重写这个基准测试:
func benchmarkFib(i int, b *testing.B) {
for n := 0; n < b.N; n++ {
Fib(i)
}
}
func BenchmarkFib1(b *testing.B) { benchmarkFib(1, b) }
func BenchmarkFib2(b *testing.B) { benchmarkFib(2, b) }
func BenchmarkFib3(b *testing.B) { benchmarkFib(3, b) }
func BenchmarkFib10(b *testing.B) { benchmarkFib(10, b) }
func BenchmarkFib20(b *testing.B) { benchmarkFib(20, b) }
func BenchmarkFib40(b *testing.B) { benchmarkFib(40, b) }
私有化 benchmarkFib
函数,可以避免运行测试时用正则匹配到它的函数签名。