全局函数
func Short() bool // 报告是否使用-test.short标志
func Verbose() bool // 报告是否使用-test.v标志
单元测试
- 测试用例必须以 _test.go结尾
- 测试用例的函数必须以 Test开头,Test后必须是大写。且形参必须为testing.T,例如:TestAdd(t testing.T)
- 一个测试用例文件中可以有多个测试用例函数
- 测试用例不需要main函数做为入口
my.go
package utilTest
func Add(n int) int{
res :=0
for i:=1;i<=n;i++{
res+=i
}
return res-1
}
func Mul(n int) int{
res :=1
for i:=1;i<=n;i++{
res*=i
}
return res
}
func Sub(i int,n int) int{
return i-n
}
my_test.go
package utilTest
import "testing"
func TestAdd(t *testing.T) {
res :=Add(10)
if res!=55{
t.Fatal("error")//输出错误信息,并终止程序(该测试,不会整个程序退出)
}
t.Log("success")//打印日志
}
func TestMul(t *testing.T) {
res :=Mul(5)
if res!=120{
t.Fatal("error")
}
t.Log("success")
}
func TestSub(t *testing.T) {
res :=Sub(100,50)
if res!=50{
t.Fatal("error")
}
t.Log("success")
}
type T
T 是传递给测试函数的一种类型,它用于管理测试状态并支持格式化测试日志。测试日志会在执行测试的过程中不断累积, 并在测试完成时转储至标准输出。
当一个测试的测试函数返回时, 又或者当一个测试函数调用 FailNow 、 Fatal 、 Fatalf 、 SkipNow 、 Skip 或者 Skipf 中的任意一个时, 该测试即宣告结束。 跟 Parallel 方法一样, 以上提到的这些方法只能在运行测试函数的 goroutine 中调用。
type T struct {
// contains filtered or unexported fields
}
func (c T) Error(args …interface{}) 调用 Error 相当于在调用 Log 之后调用 Fail
func (c T) Errorf(format string, args …interface{}) 调用 Errorf 相当于在调用 Logf 之后调用 Fail
func (c T) Fail() 将当前测试标识为失败,但是仍继续执行该测试
func (c T) FailNow() 将当前测试标识为失败并停止执行该测试
func (c T) Failed() bool Failed 用于报告测试函数是否已失败
func (c T) Fatal(args …interface{}) 调用 Fatal 相当于在调用 Log 之后调用 FailNow
func (c T) Fatalf(format string, args …interface{}) 调用 Fatalf 相当于在调用 Logf 之后调用 FailNow
func (c T) Log(args …interface{})
- 对于测试来说,格式化文本只会在测试失败或者设置了 -test.v 标志的情况下被打印出来; 2)对于基准测试来说,为了避免 -test.v 标志的值对测试的性能产生影响, 格式化文本总会被打印出来
func (c T) Logf(format string, args …interface{})
func (c T) Name() string 返回正在运行的测试或基准测试的名字
func (t T) Run(name string, f func(t T)) bool 执行名字为 name 的子测试 f
func (c T) Skip(args …interface{}) 调用 Skip 相当于在调用 Log 之后调用 SkipNow
func (c T) SkipNow() 将当前测试标识为“被跳过”并停止执行该测试
func (c T) Skipf(format string, args …interface{})
func (c T) Skipped() bool Skipped 用于报告测试函数是否已被跳过
func (t *T) Parallel() Parallel 用于表示当前测试只会与其他带有 Parallel 方法的测试并行进行测试
基准测试
- 测试用例必须以 _test.go结尾
- 测试用例的函数必须以 Test开头,Test后必须是大写。且形参必须为testing.B,例如:TestAdd(t testing.B)
- 一个测试用例文件中可以有多个测试用例函数
- 测试用例不需要main函数做为入口
- 运行:
- go test -bench . :执行所有的基准测试
- go test -bench BenchmarkSub :执行单个基准测试
- 注意:执行基准测试前,会将单元测试全部执行完,可以加 -run ^$ 来避免
my.go
func Add(n int) int{
res :=0
for i:=1;i<=n;i++{
res+=i
}
return res
}
my_test.go
package test
import "testing"
func BenchmarkTest(b *testing.B) {
// 如果有初始化操作,b.ResetTimer()将让你跳过这些运行时间
// b.ResetTimer()
b.ReportAllocs() // 在report中包含内存分配信息
for i:=1;i<b.N;i++{
//如果每次循环迭代内部都有一些高成本的其他逻辑,使用b.StopTimer()和b.StartTimer()来暂停基准计时器。
//b.StopTimer()
// 需要操作的逻辑
//b.StartTimer()
Add(10)
}
}
BenchmarkTest-12 :-12 表示12个CPU线程执行
300000000 :总共执行了300000000次,越高越好
5.49 ns/op : 每次执行消耗5.49纳秒,越低越好
0 B/op : 每次执行分配了0字节内存,越低越好
0 allocs/op :表示每次执行分配了0次对象,越低越好
type B
B 是传递给基准测试函数的一种类型,它用于管理基准测试的计时行为,并指示应该迭代地运行测试多少次。
一个基准测试在它的基准测试函数返回时,又或者在它的基准测试函数调用 FailNow、Fatal、Fatalf、SkipNow、Skip 或者 Skipf 中的任意一个方法时,测试即宣告结束。至于其他报告方法,比如 Log 和 Error 的变种,则可以在其他 goroutine 中同时进行调用。
跟单元测试一样,基准测试会在执行的过程中积累日志,并在测试完毕时将日志转储到标准错误。但跟单元测试不一样的是,为了避免基准测试的结果受到日志打印操作的影响,基准测试总是会把日志打印出来。
type B struct {
N int
// contains filtered or unexported fields
}
func (c B) Error(args …interface{}) 调用 Error 相当于在调用 Log 之后调用 Fail
func (c B) Errorf(format string, args …interface{})
func (c B) Fail() 将当前的测试函数标识为“失败”,但仍然继续执行该函数
func (c B) FailNow() 将当前的测试函数标识为“失败”,并停止执行该函数
func (c B) Failed() bool 用于报告测试函数是否已失败
func (c B) Fatal(args …interface{}) 调用 Fatal 相当于在调用 Log 之后调用 FailNow
func (c B) Fatalf(format string, args …interface{}) 调用 Fatalf 相当于在调用 Logf 之后调用 FailNow
func (c B) Log(args …interface{})
- Log 使用与 Println 相同的格式化语法对它的参数进行格式化, 然后将格式化后的文本记录到错误日志里面:
- 对于测试来说, 格式化文本只会在测试失败或者设置了 -test.v 标志的情况下被打印出来; 对于基准测试来说, 为了避免 -test.v 标志的值对测试的性能产生影响, 格式化文本总会被打印出来
func (c B) Logf(format string, args …interface{})
func (c B) Name() string 返回正在运行的测试或者基准测试的名字
func (b B) ReportAllocs() 打开当前基准测试的内存统计功能
func (b B) ResetTimer() 对已经逝去的基准测试时间以及内存分配计数器进行清零
func (b B) Run(name string, f func(b B)) bool
以并行的方式执行给定的基准测试。 RunParallel 会创建出多个 goroutine ,并将 b.N 分配给这些 goroutine 执行, 其中 goroutine 数量的默认值为 GOMAXPROCS
testing.Benchmark(func(b *testing.B) {
templ := template.Must(template.New("test").Parse("Hello, {{.}}!"))
// RunParallel will create GOMAXPROCS goroutines
// and distribute work among them.
b.RunParallel(func(pb *testing.PB) {
// Each goroutine has its own bytes.Buffer.
var buf bytes.Buffer
for pb.Next() {
// The loop body is executed b.N times total across all goroutines.
buf.Reset()
templ.Execute(&buf, "World")
}
})
})
将 RunParallel 使用的 goroutine 数量设置为 p*GOMAXPROCS ,如果 p 小于 1 ,不产生任何效果
func (c B) Skip(args …interface{}) 调用 Skip 相当于在调用 Log 之后调用 SkipNow
func (c B) Skipf(format string, args …interface{})
func (c B) SkipNow() 将当前测试标识为“被跳过”并停止执行该测试
func (c B) Skipped() bool 报告测试是否已被跳过
func (b B) StartTimer() 在调用 StopTimer 之后恢复进行计时
func (b B) StopTimer()
- 停止对测试进行计时。 当你需要执行一些复杂的初始化操作, 并且你不想对这些操作进行测量时, 就可以使用这个方法来暂时地停止计时。
更多用法
注意:直接go test 不加参数,会执行当前目录下所有的测试用例
-run regexp 执行匹配到的单元测试
-bench regexp 执行匹配到的基准测试
-benchmem 打印内存消耗情况
-benchtime d 每个测试所用时间,默认1s,例如 -benchtime 3s
-count n 测试次数
-cpuprofile file 输出cpu使用情况
-memprofile file 输出内存使用情况
-v 详细输出:在所有测试运行时记录它们
-c 生成用于测试的可执行文件
-o 指定生成用于测试的可执行文件的文件名
一般写法:
go test -v ./xxx/... -run .
指定文件夹或文件(通常为点 或则 ./xxx/...)
测试单个文件,一定要带上被测试的原文件
例如:go test -bench . -benchtime “3s” -cpuprofile profile_cpu func_test.go 后,会生成对应的文件
如何观察呢?
- go tool pprof profile_cpu 进入交互模式
- 常用命令
- top 5,打印消耗最高的前5个
- list funcName,打印funcName的详细情况
- svg 生成svg,要先安装https://graphviz.org/download/ (把bin目录放在pasth,然后dot —version)
- Graphviz.zip
执行某个文件内所有的测试:go test -v aaa_test.go
执行某个文件内某个测试方法:go test -v -run TestAdd aaa_test.go
pprof UI
go tool pprof -http=:8080 heap.out
TestMain
go test 功能,提高了开发和测试的效率。
有时会遇到这样的场景:
进行测试之前需要初始化操作(例如打开连接),测试结束后,需要做清理工作(例如关闭连接)等等。这个时候就可以使用TestMain()
package hello
import(
"fmt"
"testing"
)
func TestAdd(t *testing.T) {
r := Add(1, 2)
if r !=3 {
t.Errorf("Add(1, 2) failed. Got %d, expected 3.", r)
}
}
func TestMain(m *testing.M) {
fmt.Println("begin")
m.Run()
fmt.Println("end")
}
// output
begin
=== RUN TestAdd
— PASS: TestAdd (0.00s)
PASS
end
ok hello 0.432s
注意
//当TestMain被调用时,flag没有运行解析。
//如果TestMain依赖于命令行标记,包括那些测试包,它应该调用flag.Parse()。显式解析。
// A simple implementation of TestMain is:
func TestMain(m *testing.M) {
// call flag.Parse() here if TestMain uses flags
os.Exit(m.Run())
}
测试生成的图
框框越大,表示耗时越多
箭头越粗,表示耗时越多
记录:
-failfast flag does not apply across packages
https://github.com/golang/go/issues/33038
运行go test ./…-failfast 如果其中一个包出现故障,它将继续运行其他包的测试。
这是因为包不同,默认是以包为维度,做并行测试
另外还有一点,并发测试的标志,只适用于一个包内的测试用例,因为包不同默认就是并行的在测试
- -p 指定的是不同包的并行数
工具推荐
https://github.com/pkg/profile,这个包能快速得到你想要的图,使用很方便
代码覆盖率
指令:go test -v -coverprofile=cover.out
或:go test -cover
测试单个文件:go test -cover params_check_test.go params_check.go
=== RUN TestSum
--- PASS: TestSum(0.00s)
PASS
coverage: 100.0% of statements
ok sum 0.009s
$ ll cover.out
-rw-rw-r-- 1 pirlo pirlo 1330 Jan 12 23:11 cover.out
生成html:
指令:go tool cover -html=cover.out -o coverage.html
执行完毕会生成一个html文件, 直接打开浏览器浏览便可
Subprocess tests
如果测试代码,可能通过os.Exit 的方式退出,则需要用到子测试
参考:https://talks.golang.org/2014/testing.slide#23
go test ./…
./… 会列出本项目下所有含.go文件的目录,会忽略 本项目下的子项目目录