测试库
Go 内置的测试库:
- testing:最基本的测试库。
- net/http/httptest:基于 tesing 实现的专用于 Web 应用测试。
其他第三方测试库:
着重了解 testing 库。
testing 库介绍
testing 库有两个重要的数据结构:testing.T, tesing.B,可以进行三种测试:单元测试,基准测试和示例测试。
命名规范
若要测试某 Go 源文件,则测试文件要求以该文件名开头,以 _test.go 结尾,且两者在同一包内。
- 如 server.go 的测试文件应该命名为 server_test.go
若要测试某函数,测试函数的形式要求(注意严格遵循大小写。):
- 单元测试:TestXxx
- 基准测试:BenchmarkXxx
-
测试命令
go test 命令会执行对当前目录下所有以 _test.go 为后缀的文件进行测试,测试流程:
生成一个临时的 main 包来调用所有的测试函数
- 编译、运行、汇报结果
- 清空临时文件
单元测试
单元测试是进行功能测试,使用 testing.T 结构。
一个单元是程序的一个模块,通常是一个函数(通常的意思是有可能不是),程序中的一个部分能否独立地进行测试,是评判这个部分能否被归纳为单元的一个重要指标。
常用标志:
- -v 显示更详细的信息。
- -cover 显示覆盖率。
- -short:跳过长时间的测试。
- -run:后面接正则表达式,执行符合条件的测试函数。
- -parallel:并行测试。当单元之间没有依赖关系时可以进行并行测试,
-parallel 10
表示最多并行测试 10 个单元。 - -benchmem:显示内存信息。
常用方法:
// 记录日志
Log(args... interface{})
Logf(format string, args... interface{})
/* 作用是记录日志,区别是是否支持格式化*/
// 标记失败
Fail()
/* 仅标记当前测试状态为失败 */
// 标记失败+ + 结束测试
FailNow()
// 标记失败 + 记录日志
Error(args... interface{})
Errorf(format string, args... interface{})
/* 只标记测试状态为失败,不影响测试函数流程,不会结束测试 */
// 标识失败 + 记录日志 + 结束测试
Fatal(args... interface{})
Fatalf(format string, args... interface{})
// 跳过测试 + 记录日志
Skip(args... interface{})
Skipf(format string, args... interface{})
// 跳过测试 + 结束测试
SkipNow()
例子:以一个简单的加法函数为例子
// main.go
package main
import (
"fmt"
)
func main() {
fmt.Println(3, 4)
}
func add(x, y int) int {
return x + y
}
// main_test.go
package main
import (
"testing"
)
func TestAdd(t *testing.T) {
arg1 := 3
arg2 := 4
expected := 7
result := add(arg1, arg2)
if result != expected {
t.Errorf("add(%d, %d) = %d, wanted %d", arg1, arg2, result, expected)
}
}
/*
执行:go test -v
结果如下:
=== RUN TestAdd
--- PASS: TestAdd (0.00s)
PASS
ok test 0.038s
*/
所谓单元测试,基本上就是为被测函数手动构造测试用例。
基准测试
基准测试是进行性能测试,使用 testing.B 结构。
常用标志:
- -bench:后面接正则表达式,筛选符合条件的测试文件,
-bench .
表示全测。 - -benchtime:设置测试时间,默认 1 秒。如
-benchtime 5s
测试 5 秒,-benchtime 30x
表示只执行 30 次。
例子:观察 for i 和 for range 的性能差异
// main.go
package main
import (
"fmt"
)
var nums []int
func main() {
nums = make([]int, 10)
forI()
forRange()
}
func forI() {
for i := 0; i < len(nums); i++ {
fmt.Println(i, nums[i])
}
}
func forRange() {
for i, num := range nums {
fmt.Println(i, num)
}
}
// main_test.go
package main
import (
"testing"
)
func BenchmarkForI(b *testing.B) {
for i := 0; i < b.N; i++ {
forI()
}
}
func BenchmarkForRange(b *testing.B) {
for i := 0; i < b.N; i++ {
forRange()
}
}
/*
执行:go test -bench .
结果如下:
goos: windows
pkg: test
cpu: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz
BenchmarkForI-12 656349938 1.817 ns/op
BenchmarkForRange-12 656249076 1.835 ns/op
ok test 2.815s
*/
- 第一列函数名后面 -12 表示本次测试 runtime.GOMAXPROCS 被设置为 12,即同时运行 12 个 goroutine 并行。
- 第二列的数字表示循环的次数,即 b.N 的实际取值,b.N 是由程序决定的,一般是 10 的指数级。
- 第三列表示每次循环花费的时间,1.817 ns/op 表示平均每次循环花费 1.817 纳秒。
看起来好像性能差不多,如果自定义一个如下结构体,会看见天差地别!
package main
import (
// "fmt"
)
type Person struct {
Name string
scores [4096]int
}
var persons []Person
func main() {
persons = make([]Person, 100000000)
forI()
forRange()
}
func forI() {
tmp := 0
for i := 0; i < len(persons); i++ {
tmp = persons[i].scores[0]
}
_ = tmp
}
func forRange() {
tmp := 0
for _, person := range persons {
tmp = person.scores[0]
}
_ = tmp
}
/*
goos: windows
goarch: amd64
pkg: test
cpu: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz
BenchmarkForI-12 1000000000 0.5202 ns/op
BenchmarkForRange-12 733372609 1.602 ns/op
PASS
ok test 2.094s
*/
性能差异初现,相差两倍左右。
示例测试
广泛用于 Go 源码和开源项目,目的是展示某个包或某个函数的用法。
测试原理
目前不打算学,太多了。