测试库
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.gopackage mainimport ("fmt")func main() {fmt.Println(3, 4)}func add(x, y int) int {return x + y}// main_test.gopackage mainimport ("testing")func TestAdd(t *testing.T) {arg1 := 3arg2 := 4expected := 7result := 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)PASSok test 0.038s*/
所谓单元测试,基本上就是为被测函数手动构造测试用例。
基准测试
基准测试是进行性能测试,使用 testing.B 结构。
常用标志:
- -bench:后面接正则表达式,筛选符合条件的测试文件,
-bench .表示全测。 - -benchtime:设置测试时间,默认 1 秒。如
-benchtime 5s测试 5 秒,-benchtime 30x表示只执行 30 次。
例子:观察 for i 和 for range 的性能差异
// main.gopackage mainimport ("fmt")var nums []intfunc 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.gopackage mainimport ("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: windowspkg: testcpu: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHzBenchmarkForI-12 656349938 1.817 ns/opBenchmarkForRange-12 656249076 1.835 ns/opok test 2.815s*/
- 第一列函数名后面 -12 表示本次测试 runtime.GOMAXPROCS 被设置为 12,即同时运行 12 个 goroutine 并行。
- 第二列的数字表示循环的次数,即 b.N 的实际取值,b.N 是由程序决定的,一般是 10 的指数级。
- 第三列表示每次循环花费的时间,1.817 ns/op 表示平均每次循环花费 1.817 纳秒。
看起来好像性能差不多,如果自定义一个如下结构体,会看见天差地别!
package mainimport (// "fmt")type Person struct {Name stringscores [4096]int}var persons []Personfunc main() {persons = make([]Person, 100000000)forI()forRange()}func forI() {tmp := 0for i := 0; i < len(persons); i++ {tmp = persons[i].scores[0]}_ = tmp}func forRange() {tmp := 0for _, person := range persons {tmp = person.scores[0]}_ = tmp}/*goos: windowsgoarch: amd64pkg: testcpu: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHzBenchmarkForI-12 1000000000 0.5202 ns/opBenchmarkForRange-12 733372609 1.602 ns/opPASSok test 2.094s*/
性能差异初现,相差两倍左右。
示例测试
广泛用于 Go 源码和开源项目,目的是展示某个包或某个函数的用法。
测试原理
目前不打算学,太多了。
