单元测试
对于程序或软件的测试分很多种,比如:单元测试、API测试、集成测试、灰度测试等。这里主要针对单元测试进行讲解。
Go 语言是一门很重视程序测试的语言,它不但自带了testing包,还有专门用于程序测试的命令go test。要想真正用好一个工具,就需要了解它的核心逻辑。
测试源码文件
单元测试,又称程序员测试。就是程序员程序员们本该做的自我检查的工作之一。
我们可以为Go程序编写3类测试:
- 功能测试(test)
- 基准测试(benchmark),也称性能测试
- 示例测试(example)
前两类测试,从名称上就应该可以猜到它们的用途。示例测试,严格来讲也是一种功能测试,只不过它更关注程序打印出来的内容。
一般情况下,一个测试源码文件只会针对于某个命令源码文件,或库源码文件做测试,所以一般并且也应该把他们放在同一个代码包内。
测试源码文件的主名称应该以被测试源码文件的主名称为签到,并且必须以“_test”为后缀。
每个测试源码文件都必须至少包含一个测试函数。并且,从语法上讲,每个测试源码文件中,都可以包含用来做任何一类测试的测试函数,就是说把上面说的3类测试函数都塞进去也没问题,只要把控好测试函数的分组和数量就可以了。
我们可以依据这些测试函数针对的不同程序实体,把他们分成不同额逻辑组。并且利用注释以及帮助类的变量或函数来做分割。同时,还可以依据被被测试的文件中程序实体的先后顺序来安排测试源码文件中测试函数的顺序。
测试函数的名称和签名
对于测试函数的名称和签名,Go语言也有明文的规定:
- 功能测试函数,名称必须以Test为前缀,并且只有一个*testing.T类型的参数。
- 性能测试函数,名称必须以Benchmark为前缀,并且只有一个*testing.B类型的参数。
- 示例测试函数,名称必须以Example为前缀,对参数没有强制规定。
go test的主要流程
前面两小节的内容,其实是go test命令的基本规则。只有测试源码文件的名称对了,测试函数的名称和签名也对了,在运行go test命令的时候,其中的测试代码才会被运行。接着讲主要流程。
当go test命令在开始运行时,会先做一下准备工作,比如:确定内部需要用到的命令,检查指定的代码包或源码文件的有效性,以及判断我们给予的标记是否合法,等等。
在准备工作完成后,go test命令就会针对每个被测试代码包,依次的进行构建、执行包中符合要求的测试函数,清理临时文件,打印测试结果。这就是通常情况下的主要测试流程。
这里的“依次”二字,对于每个被测试代码包,go test命令会串行地执行测试流程中的每个步骤。但是为了加快测试速度,通常会并发的对多个被测试代码包进行功能测试。但是在最后打印测试结果的时候,会依照给定的顺序逐个打印。
另一个方面,由于并发的测试会让性能测试的结果存在偏差,所以性能测试一般都是串行进行的。具体说就是在所有构建步骤都做完之后,go test命令才会真正地开始进行性能测试。并且下个一个代码包的性能测试的进行,会等到上一个代码包的性能测试的结果打印完成才会开始,并且性能测试函数的执行也都会是串行的。
功能测试
先给出示例,然后主要讲测试规则和流程
功能测试示例
这里把示例单独给出,首先是命令源码文件:
// demo.go
package main
import (
"errors"
"flag"
"fmt"
)
var name string
func init() {
flag.StringVar(&name, "name", "bobody", "Your Name:")
}
func main() {
flag.Parse()
greeting, err := hello(name)
if err != nil {
fmt.Printf("ERROR: %s\n", err)
return
}
fmt.Println(greeting, introduce())
}
func hello(name string) (string, error) {
if name == "" {
return "", errors.New("empyt name")
}
return fmt.Sprintf("Hello %s.", name), nil
}
func introduce() string {
return "Welcome"
}
然后是测试源码文件,这里对文件名和函数名称都是有要求的。看函数名称这里都是功能测试函数:
// demo_test.go
package main
import (
"fmt"
"testing"
)
func TestHello(t *testing.T) {
var name string
greeting, err := hello(name)
if err == nil {
t.Errorf("The error is nil, but it should not be. (name=%q)", name)
}
if greeting != "" {
t.Errorf("Nonempty greeting, but it should not be. (name=%q)", name)
}
name = "Clark"
greeting, err = hello(name)
if err != nil {
t.Errorf("The error is not nil, but it should be. (name=%q)", name)
}
if greeting == "" {
t.Errorf("Empty greeting, but it should not be. (name=%q)", name)
}
expected := fmt.Sprintf("Hello %s.", name)
if greeting != expected {
t.Errorf("greeting: %q, expected: %q, not same.", greeting, expected)
}
t.Logf("The expected greeting is %q.\n", expected)
}
func TestIntroduce(t *testing.T) {
intro := introduce()
expected := "Welcome"
if intro != expected {
t.Errorf("intro: %q, expected: %q, not same", intro, expected)
}
t.Logf("The expected introduce is %q.\n", expected)
}
功能测试的结果
先来看下面的测试命令和结果:
PS H:\Go\src> go test Go36\article23\example01
ok Go36/article23/example01 0.051s
PS H:\Go\src>
在这里,输入了命令go test Go36\article23\example01
,表示想对这个导入路径的代码包进行测试。另外go test命令,默认执行当前目录下以_test.go结尾的测试文件,所以可以不带后面的参数。
命令的下面一行,就是此次测试的简要结果。简要结果有三块内容:
- 最左边的ok表示此次测试成功
- 中间的内容,显示了被测代码包的导入路径
- 最右边,展现了此次对该代码包测试所耗的时间
缓存
对于最右边的时间,有时候可能是这样的:
ok Go36/article23/example01 (cached)
这表明,由于测试代码与被测试代码都没有任何变动,所以go test命令直接把之前缓存测试成功的结果打印出来了。关于这个缓存,可以运行下面的命令来查看缓存目录的路径:
go env GOCACHE
可能windows系统上没有缓存?我在win10系统上貌似是没有缓存。go env里也没有GOCACHE这个环境变量。尝试手动去系统里加上这个变量,再用go env也是显示不出来,系统重启也试过了
缓存的数据可以正确的反应出测试时的各种源码文件、构建环境、编译器选项等情况。一旦有任何变动,缓存的数据就会失效,go test命令就会再次真正地执行操作。go命令会定期地删除最近未使用的缓存数据,不过也是可以手动删除所有缓存数据的,命令如下:
go clean -cache
上面的命令清除的是go命令所有的缓存,对于测试成功的结果,go命令也会缓存。可以只删除所有测试结果的缓存,而不删除任何构建结果的缓存。命令如下:
go clean -testcache
这里的命令也有问题,打印go clen的帮助如下:
PS H:\Go\src\Go36> go clean --help
usage: clean [-i] [-r] [-n] [-x] [build flags] [packages]
就上面这些参数,这里的命令参数貌似都是没有的,不清楚是版本问题,还是系统问题。
这里是网上查到的方法:显示禁用缓存的惯用方法是使用-count=1标志。就是作为参数加到go test命令后面。
总之,不用太在意缓存数据的存在,因为这并不会妨碍go test命令打印正确的测试结果。
测试失败
如果功能测试的函数的参数名称为t,那么调用t.Fail方法,可以使测试失败。在之前的测试代码的最后,再新增一个功能测试函数TestFail,具体如下:
func TestFail(t *testing.T) {
t.Fail()
t.Log("测试失败...")
}
现在再运行测试,就会显示测试失败,具体如下:
PS G:\Steed\Documents\Go\src\Go36\article24\example01> go test
--- FAIL: TestFail (0.00s)
demo_test.go:43: 测试失败...
FAIL
exit status 1
FAIL Go36/article24/example01 0.115s
PS G:\Steed\Documents\Go\src\Go36\article24\example01>
上面的内容显示,TestFail函数的测试失败。
对于失败测试的结果,是不会进行缓存的,所以每次测试都是全新的结果。
t.FailNow()
这里还有一个方法:t.FailNow()。
与t.Fail()不同,在t.FailNow()执行后,当前函数会立即终止执行。t.FailNow方法只是终止它所在的函数,不会影响到其他函数的处理。但是当前函数执行到t.FailNow()之后,后面的代码就没有执行的的机会了。如果把上面例子中的t.Fail()替换成t.FailNow(),则不会打印测试日志,因为t.Log没有机会执行。
测试日志
如果测试失败了,那么go test会将导致失败的测试函数中的常规测试日志一并打印出来,就是t.Log方法调用的内容。
t.Log方法以及t.Logf方法,就是打印常规的测试日志。只有在测试失败的时候才当测试成功的时候,不打印这类日志。默认在测试成功的时候不打印这类日志,不过可以加上-v参数在成功时也打印测试日志。
t.Error 和 t.Errorf
如果想在测试失败的通常打印失败测试日志,那么可以直接调用t.Error或t.Errorf方法。相当于t.Log或t.Logf和t.Fail方法的连续调用。
t.Fatal 和 t.Fatalf
这两个方法的作用是在打印失败错误日志后立即终止当前测试函数的执行并宣告失败。就是相当于调用了t.FailNow方法。
性能测试
性能测试与功能测试的结果有很多相似的地方,先把示例代码准备好,然后关注性能测试结果里的特殊之处。
测试示例
先准备用来做性能测试的包,这里用不同的方法来实现斐波那契数列。一个用递归方法,效率比较差,还有一个是for循环实现的,逻辑稍微复杂一点但是效率更高:
// Go36/article24/example02/fibonacci/fibonacci.go
package fibonacci
func FibRescuvie (x uint) uint64 {
if x > 93 { return 0 } // uint64最多可以存第93个数,再高就溢出了
switch x {
case 0:
return 0
case 1, 2:
return 1
default:
return FibRescuvie(x-2) + FibRescuvie(x-1)
}
}
func FibForLoop (x uint) uint64 {
first := uint64(1)
second := uint64(1)
var r uint64
for i := uint(3); i <= x; i++ {
r = first + second
first, second = second, r
}
return r
}
然后是性能测试的代码:
// Go36/article24/example02/fibonacci/fibonacci_test.go
package fibonacci
import "testing"
func BenchmarkFibRescuvie(b *testing.B) {
for i := 0; i < b.N; i++ {
FibRescuvie(40)
}
}
func BenchmarkFibForLoop(b *testing.B) {
for i := 0; i < b.N; i++ {
FibForLoop(40)
}
}
接着就来运行测试:
PS H:\Go\src> go test -bench . -run=^$ Go36/article24/example02/fibonacci
goos: windows
goarch: amd64
pkg: Go36/article24/example02/fibonacci
BenchmarkFibRescuvie-8 3 408665433 ns/op
BenchmarkFibForLoop-8 100000000 19.2 ns/op
PASS
ok Go36/article24/example02/fibonacci 4.484s
PS H:\Go\src>
性能测试的命令
先说运行测试的命令,上面的命令加了2个标记: -bench .
,必须要加上这个标记,才会进行性能测试。该标记的值是个点(.)表示要执行的性能测试函数,这里用了点,表示执行任意名称的性能测试函数。还可以这么写-bench="."
、-bench FibRescuvie
、-bench=FibRescuvie
。 -run=^$
,这个标记由于表明需要执行哪些功能测试函数,同样也是以函数名称为依据。这里标记的值是^$,分别是正则表达式的开头和结束符,就是一个空的名称。也就是没有任何函数名称会符合要求,就是不执行任何的功能测试函数。如果不加-run标记,那么就会运行代码包中所有的功能测试函数。
这里的两个标记的值都是正则表达式,并且也只能以正则表达式为值。
性能测试结果
关于结果,重点说下这行,重新贴出来:
BenchmarkFibRescuvie-8 3 408665433 ns/op
CPU
BenchmarkFibRescuvie-8被称为单个性能测试的名称。它表示命令执行了性能测试函数BenchmarkFibRescuvie,并且当时所有的最大P数量为8。这里的P是processor的缩写,最大P数量相当于可以同时运行goroutine的逻辑CPU的最大个数(确认了一下,机器确实是8核的)。这里的逻辑CPU,也可以被称为CPU核心,但它并不等同于计算机中真正的CPU核心,只是Go语言运行时系统内部的一个概念,代表着它同时运行goroutine的能力。一台计算机的CPU核心的个数,意味着它能在同一时刻执行多少条程序指令,代表着它并行处理程序指令的能力。可以通过调用runtime.GOMAXPROCS
函数改变最大P数量,也可以在运行go tes命令的时候,加入标记-cpu来设置一个最大P数量的列表,以供命令在多次测试时使用。
执行次数
中间的数字是被测试函数被执行的实际次数。go test命令在执行性能测试函数的时候会给它一个正整数,例子中就是b.N。我们应该在测试函数中配合着编写代码,比如:
for i := 0; i < b.N; i++ {
FibForLoop(40)
}
它会先迭代b.N次的循环中调用被测试函数。go test命令先尝试把b.N设置为1,然后执行测试函数。如果测试函数的执行时间没有超过上限,上限默认为1秒,那么命令就会改大b.N的值,然后再次执行测试函数,如此往复,知道这个时间大于或等于上限为止。可以简称该值为执行次数,但是它指的是被测试函数的执行次数,而不是性能测试函数的执行次数。
运行平均时间
最后是游标的数字,表明单次执行被测试函数的平均耗时,这里的ns是纳秒。这个值其实就是将测试的总时间除以执行次数而得出的。
go test命令
介绍几个可以被go test命令接受的重要标记,进一步说明功能测试和性能测试在不同条件下的测试流程。
回顾G-P-M模型
上篇的go test命令提到过一个-cpu标记,它是用来设置执行最大P数量的列表的。
这里的P是process的缩写,在讲go语句(就是goroutine)的时候说过。每个process都是一个可承载若干个G,并且能够使这些G适时地与M进行对接并得到真正运行的中介。正式由于P的存在,G和M才可以呈现出多对多的关系,并能够及时、灵活地进行组合和分离。
这里的G是goroutine的缩写,可以被理解为Go语言自己实现的用户级线程。
这里的M是machine的缩写,代表着系统级线程,或者说操作系统内核级别的线程。
Go语言并发程序模型中的P,正式goroutine的数量能够数十万计的关键所在。P的数量意味着Go程序背后运行时系统中,会有多少个由于承载可运行的G的队列存在。每一个队列都相当于一条流水线,源源不断地把可运行的G输送给空闲的M,并使两者对接。一旦对接完成,被对接的G就真正的运行在操作系统的内核级线程之上了。每条流水线之间虽然有联系,但都是独立运作的。
因此,最大P数量就代表着Go语言运行时系统同时运行goroutine的能力,也可以被视为其中逻辑CPU的最大个数。go test命令的-cpu标记就是用于设置这个最大个数的。在默认情况下,最大P数量就等于当前计算机CPU核心的实际数量。也可以大于或者小于实际数量,如此可以在一定程度上模拟拥有不同CPU核心数的计算机。
设置-cpu标记的值
标记-cpu应该是一个正整数的列表,用逗号分隔的多个整数字面量。
针对值中的每一个正整数,go test会先设置最大P数量为该数值,然后再执行测试函数。如果测试函数有多个,go test会先以所有-cpu参数值去执行第一个测试函数,然后再用同样的方式执行后面的测试函数。
go test执行测试函数时,关于-cpu标记的具体步骤。不论是否设置了-cpu标记,go test在进行准备工作的时候,会读取-cpu标记的值,并把它转换为一个int为元素类型的切片。如果没有-cpu标记,那么切片就只包含一个元素,值是最大P数量的默认值。在准备某个测试函数的时候,无论该函数是功能测试函数还是性能测试函数,go test会迭代切片的值。在每次迭代的时候,先依据当前元素的值设置最大P数量,然后再去执行测试函数。
这里注意,虽然上一小节说可以大于CPU核心的实际数量。但是最好不要超过,因为一旦超出计算机实际的并行处理能力,Go程序在性能上就无法再获得到显著的提升了。而且为了管理过多的P,系统还会耗费额外的计算资源。
-count标记
go test每一次对性能测试函数的执行,对一个探索的过程。它会在测试函数的执行时间上限不变的前提下,尝试找到被测试程序的最大执行次数。在这个过程中,性能测出函数可能会被执行多次。可以把这样一个探索的过程称为探索式执行。
这里有一个标记-count,就是专门用于重复执行测试函数的。它的值必须大于或等于0,默认值为1。如果设置了-count,对于每一个测试函数,命令都会在设置不同的条件下(比如不同的最大P数量)分别重复执行若干次。所以如果配合上-cpu标记,那么执行的次数就是-cpu切片里元素的数量和-count数值的乘积,这个是对于功能测试的执行次数。如果是性能测试,这个乘积就是测试函数执行探索式执行的次数,实际次数就要再乘以一次探索式执行中测试函数实际执行的次数了。
下面的结果是设置了-cpu和-count之后的测试结果,可以看下期中的执行次数和顺序:
PS G:\Steed\Documents\Go\src\Go36\article24\example02\fibonacci> go test -bench . -cpu 1,2,3 -count 2
goos: windows
goarch: amd64
pkg: Go36/article24/example02/fibonacci
BenchmarkFibRescuvie 2 642132400 ns/op
BenchmarkFibRescuvie 2 639623600 ns/op
BenchmarkFibRescuvie-2 2 637135800 ns/op
BenchmarkFibRescuvie-2 2 637134900 ns/op
BenchmarkFibRescuvie-3 2 632637750 ns/op
BenchmarkFibRescuvie-3 2 630628650 ns/op
BenchmarkFibForLoop 50000000 31.0 ns/op
BenchmarkFibForLoop 50000000 31.1 ns/op
BenchmarkFibForLoop-2 50000000 30.7 ns/op
BenchmarkFibForLoop-2 50000000 31.3 ns/op
BenchmarkFibForLoop-3 50000000 31.0 ns/op
BenchmarkFibForLoop-3 50000000 31.3 ns/op
PASS
ok Go36/article24/example02/fibonacci 22.063s
PS G:\Steed\Documents\Go\src\Go36\article24\example02\fibonacci>
-parallel标记
在运行go test命令的时候,可以追加-parallel标记。该标记的作用是,设置同一个被测试代码包中的功能测试函数的最大并发执行数,默认值是测试运行时的最大P数量。
对于功能测试,为了加快测试速度,通常会并发的测试多个被测代码包。但是默认情况下,同一个代码包这的过个功能测试函数是串行的执行的。除非在一些功能测试函数中显式的调用t.Parallel方法。实际也很简单,在函数开头加一行:
func TestIntroduce(t *testing.T) {
t.Parallel() // 其它不变,就加这一句
intro := introduce()
expected := "Welcome"
if intro != expected {
t.Errorf("intro: %q, expected: %q, not same", intro, expected)
}
t.Logf("The expected introduce is %q.\n", expected)
}
这个时候,这些包含了t.Parallel方法调用的功能测试函数就会被go test并发的执行,而并发执行的最大数量是用-parallel标记决定的。不过同一个功能测试函数的多次执行之间一定是串行的。
另外还可以在一个功能测试函数里,并发的起多个子测试。下面是一段代码的示例:
// 这个是要展示的函数
func TestGetPrimesParallel(t *testing.T) {
for _, max := range []int{1, 2, 3, 4, 5} {
max := max * 200
// 这些子测试会被并发地执行,
// 并且只有它们都执行完毕之后当前的测试函数才会执行完成。
// 当前的测试函数并不会与其他测试函数一起被并发的执行。
t.Run(fmt.Sprintf("TestGetPrimesWith%d", max),
func(t *testing.T) {
t.Parallel()
primes := GetPrimes(max)
err := comparePrimes(primes)
if err != nil {
t.Error(err)
} else {
t.Logf("The primes less than %d are all correct.", max)
}
}
)
}
}
// 下面的切片和一个函数也是测试源码文件里要用到的部分
var expectedPrimes = []int{
2, 3, 5, 7, 11, 13, 17, 19,
23, 29, 31, 37, 41, 43, 47, 53,
59, 61, 67, 71, 73, 79, 83, 89,
97, 101, 103, 107, 109, 113, 127, 131,
137, 139, 149, 151, 157, 163, 167, 173,
179, 181, 191, 193, 197, 199, 211, 223,
227, 229, 233, 239, 241, 251, 257, 263,
269, 271, 277, 281, 283, 293, 307, 311,
313, 317, 331, 337, 347, 349, 353, 359,
367, 373, 379, 383, 389, 397, 401, 409,
419, 421, 431, 433, 439, 443, 449, 457,
461, 463, 467, 479, 487, 491, 499, 503,
509, 521, 523, 541, 547, 557, 563, 569,
571, 577, 587, 593, 599, 601, 607, 613,
617, 619, 631, 641, 643, 647, 653, 659,
661, 673, 677, 683, 691, 701, 709, 719,
727, 733, 739, 743, 751, 757, 761, 769,
773, 787, 797, 809, 811, 821, 823, 827,
829, 839, 853, 857, 859, 863, 877, 881,
883, 887, 907, 911, 919, 929, 937, 941,
947, 953, 967, 971, 977, 983, 991, 997,
}
func comparePrimes(primes []int) (err error) {
for i, prime := range primes {
expectedPrime := expectedPrimes[i]
if prime != expectedPrime {
err = fmt.Errorf(
"%dth prime number %d is not the expected value %d",
i, prime, expectedPrime)
break
}
}
return
}
// 下面是命令源码文件里被测试的函数
// GetPrimes 用于获取小于或等于参数max的所有质数。
// 本函数使用的是爱拉托逊斯筛选法(Sieve Of Eratosthenes)。
func GetPrimes(max int) []int {
if max <= 1 {
return []int{}
}
marks := make([]bool, max)
var count int
squareRoot := int(math.Sqrt(float64(max)))
for i := 2; i <= squareRoot; i++ {
if marks[i] == false {
for j := i * i; j < max; j += i {
if marks[j] == false {
marks[j] = true
count++
}
}
}
}
primes := make([]int, 0, max-count)
for i := 2; i < max; i++ {
if marks[i] == false {
primes = append(primes, i)
}
}
return primes
}
上面的代码中,由于测试函数本身,就是TestGetPrimesParallel这个函数里没有显式的调用t.Parallel方法,所以这个测试函数是不会与别的测试函数一起被并发的执行的。并发执行的只是里面的那些显式的调用了t.Parallel方法的子测试。
另外,这个标记对性能测试是无效的。对于性能测试来说,也是可以并发进行的,不过机制上不同。这涉及了b.RunParallel方法、b.SetParallelism方法和-cpu标记的联合运用,这里没有展开讲。要进一步了解,建议去看testing包的文档了:https://golang.google.cn/pkg/testing/
性能测试函数中的计时器
如果去查看testing包的文档,在里面会发现其中的testing.B类型有这么几个指针方法:
- func (b *B) StartTimer()
- func (b *B) StopTimer()
- func (b *B) ResetTimer()
这些方法都是用于操作当前性能测试函数专属的计时器的。这三个方法在开始记录、停止记录以及重新记录执行时间的同时,也会对堆内存分配字节数和分配次数的记录起到相同的作用。
看下面的例子,在之前的性能测试函数之前加了4行代码:
func BenchmarkFibForLoop(b *testing.B) {
b.StopTimer()
time.Sleep(time.Millisecond * 300) // 模拟一个费时的操作
var n uint = 40
b.StartTimer()
for i := 0; i < b.N; i++ {
FibForLoop(n)
}
}
这里,先停止了计时器。然后time.Sleep,接着给n赋值,这里其实是模拟了一个比较耗时的额外操作,为的是获取到n的值。然后又启动了计时器。这里的意义是在为n赋值的这个过程中,计时器是停止的。所以这部分时间不会包含在测试函数的执行时间中,这样就避免了这个耗时的赋值过程对测试结果的影响。这里被测试程序在执行时间限制内,对被测出函数的最大执行次数不会受到这个段停止计时的时间的影响。可以注释掉b.StopTimer()和b.StartTimer()比较性能测试的结果,主要看运行次数:
PS G:\Steed\Documents\Go\src\Go36\article25\example01\fibonacci> go test -bench FibForLoop
goos: windows
goarch: amd64
pkg: Go36/article25/example01/fibonacci
BenchmarkFibForLoop-4 30000000 40.5 ns/op
PASS
ok Go36/article25/example01/fibonacci 6.376s
PS G:\Steed\Documents\Go\src\Go36\article25\example01\fibonacci> go test -bench FibForLoop
goos: windows
goarch: amd64
pkg: Go36/article25/example01/fibonacci
BenchmarkFibForLoop-4 50000000 33.9 ns/op
PASS
ok Go36/article25/example01/fibonacci 3.366s
PS G:\Steed\Documents\Go\src\Go36\article25\example01\fibonacci>
下面的结果是没有注释代码时执行的结果,和之前测试的结果是一样的。上面的结果是注释掉b.StopTimer()和b.StartTimer()之后执行的,由于每次为n赋值都会耗费额外的时间,并且计算在测试函数的执行时间里了,所以单次执行的时间长了,总的执行次数也就变少了。
在性能测试函数中,可以通过b.StopTimer方法和b.StartTimer方法的联合运用,去除掉任何一段代码的执行时间。还有一个b.ResetTimer方法,灵活性就要差一些,值能用于去除在调用它之前的那些代码的执行时间。不过,无论在调用它的时候,计时器是否正在运转,它都可以起作用。
其它标记
最后还有2个标记是通过思考题给出的,所以没有详细的介绍:
- -benchmem : 输出性能测试的内存分配统计信息。
- -benchtime : 用于指定性能测试的探索式测试执行时间上限
命令示例:
$ go test -bench . -benchmem -benchtime 10s
有疑问加站长微信联系(非本文作者)