目录
前言
1.go test
1.1 测试函数test
1.2 黑盒&白盒
1.3 测试配置
1.4 独立测试包
2.测试覆盖率
3.基准测试
4.pprof剖析
很多语言都有测试工具,如Java的JUnit测试框架的自动化测试,也就是写一些小的程序用来检测产品代码的行为和预期一致,设计执行某些特定的功能case或者是通过随机输入验证边界。
Go的测试技术相对低级,依赖go test命令和一系列测试函数规范(convention)。这种相对轻量级的机制高效,且易扩展到基准测试和示例文档。
每个包都可以有其测试代码,go test命令会遍历所有的*_test.go
文件中符合上述命名规则的函数,生成一个临时的main包用于调用相应的测试函数,接着构建并运行、报告测试结果,最后清理测试中生成的临时文件。
*_test.go
文件中,有三种类型的函数:测试函数、基准测试(benchmark)函数、示例函数。
函数 | 功能 |
---|---|
测试tests | Test*(t *testing.T) 逻辑行为是否正确 |
基准benchmarks | Benchmark*() 性能:多次运行基准函数以计算一个平均的执行时间 |
示例examples | Example*() 提供一个由编译器保证正确性的示例文档 |
每个测试函数必须导入testing包,命名以Test为前缀,后缀必须首字母大写。
func TestName(t *testing.T) {
// ...
}
t参数用于报告测试失败信息和附加的日志信息
go test选项 | 功能 |
---|---|
-v | 打印每个测试函数的名字和运行时间 |
-run egrex | 只执行正则匹配的测试函数 |
通常可以设计多个case,给定输入和预期结果,t.Errorf输出错误信息;和其他编程语言或测试框架的assert断言不同,t.Errorf调用也没有引起panic异常或停止测试的执行。当需要停止时,可以使用t.Fatal或t.Fatalf停止当前测试函数。
func TestIsPalindrome(t *testing.T) {
// 功能case设计
var tests = []struct {
input string // 输入
want bool // 预期结果
}{
{"", true},
{"a", true},
{"aa", true},
{"ab", false},
...
{"été", true},
{"Et se resservir, ivresse reste.", true},
{"palindrome", false}, // non-palindrome
{"desserts", false}, // semi-palindrome
}
for _, test := range tests {
if got := IsPalindrome(test.input); got != test.want {
t.Errorf("IsPalindrome(%q) = %v", test.input, got) // 错误信息,不终止执行
}
}
}
黑盒和白盒这两种测试方法是互补的,
黑盒测试只需要测试包公开的文档和API行为,不关心内部实现;
白盒测试有访问包内部函数和数据结构的权限,因此可以做到一些普通客户端无法实现的测试。
黑盒测试一般更健壮,随着软件实现的完善,测试代码很少需要更新。它们可以帮助测试者了解真实客户的需求,也可以帮助发现API设计的一些不足之处。相反,白盒测试可以对内部一些棘手的实现提供更多的测试覆盖。
以上的测试函数是黑盒测试,因为仅使用了包导出的函数,白盒(同一个包)可以调用未导出的函数,访问未导出变量。
通常生产和测试的配置需要区别,如db路径、邮件等,如果写在全局变量,需要在测试代码中进行覆盖和恢复。
下面处理模式可以用来暂时保存和恢复所有的全局变量,包括命令行标志参数、调试选项和优化参数;安装和移除导致生产代码产生一些调试信息的钩子函数;还有有些诱导生产代码进入某些重要状态的改变,比如超时、错误,甚至是一些刻意制造的并发行为等因素。
func TestCheckQuotaNotifiesUser(t *testing.T) {
// Save and restore original notifyUser.
saved := notifyUser
defer func() { notifyUser = saved }()
// Install the test's fake notifyUser.
var notifiedUser, notifiedMsg string
notifyUser = func(user, msg string) {
notifiedUser, notifiedMsg = user, msg
}
// ...rest of test...
}
Go语言规范是禁止包的循环依赖的,如果A包的测试代码依赖B包,B包已经A包,这样就形成了循环依赖,所以go提供了外部测试包的方式解决这个问题。可以在A包所在路径创建A_test外部包,其中包名的_test
后缀告诉go test工具它应该建立一个额外的包来运行测试。外部测试包不能被其余的导入引用。
外部测试包可以更灵活地编写测试,特别是集成测试(需要测试多个组件之间的交互),可以像普通应用程序那样自由地导入其他包。
语句的覆盖率是指在测试中至少被运行一次的代码占总代码数的比例。
go test -run=Converage 标志参数通过在测试代码中插入生成钩子来统计覆盖率数据。在运行每个测试前,它将待测代码拷贝一份并做修改,在每个词法块都会设置一个布尔标志变量。当被修改后的被测试代码运行退出时,将统计日志数据写入c.out文件,并打印一部分执行的语句的一个总结。
// 用-coverprofile标志参数,生成覆盖率文件c.out
go test -run=Coverage -coverprofile=c.out gopl.io/ch7/eval
// 测试覆盖率工具,打印了测试日志,生成一个HTML报告
go tool cover -html=c.out
go tool cover 测试覆盖率工具,打印了测试日志,生成一个HTML报告。
绿色的代码块被测试覆盖到了,红色的则表示没有被覆盖到。
//!+bench
func BenchmarkIsPalindrome(b *testing.B) {
for i := 0; i < b.N; i++ {
IsPalindrome("A man, a plan, a canal: Panama")
}
}
cd $GOPATH/src/gopl.io/ch11/word2
go test -bench=.
go test -bench=. -benchmem // 内存统计
通过-bench
命令行标志参数手工指定要运行的基准测试函数,其中“.”模式将可以匹配所有基准测试函数。以下表示迭代到5000000次,耗时1.749s,平均
-benchmem
命令行标志参数将在报告中包含内存的分配数据统计。
go tool pprof能够分析出最消耗的函数
go test -run=NONE -bench=ClientServerParallelTLS64 -cpuprofile=cpu.log net/http
go tool pprof -text -nodecount=10 ./http.test cpu.log// 剖析日志文件