单元测试 go test工具 Go语言中的测试依赖go test
命令。编写测试代码和编写普通的Go代码过程是类似的,并不需要学习新的语法、规则或工具。
go test命令是一个按照一定约定和组织的测试代码的驱动程序。在包目录内,所有以_test.go
为后缀名的源代码文件都是go test
测试的一部分,不会被go build
编译到最终的可执行文件中。
在*_test.go
文件中有三种类型的函数,单元测试函数、基准测试函数和示例函数。
类型
格式
作用
测试函数
函数名前缀为Test
测试程序的一些逻辑行为是否正确
基准函数
函数名前缀为Benchmark
测试函数的性能
示例函数
函数名前缀为Example
为文档提供示例文档
go test
命令会遍历所有的*_test.go
文件中符合上述命名规则的函数,然后生成一个临时的main包用于调用相应的测试函数,然后构建并运行、报告测试结果,最后清理测试中生成的临时文件。
测试函数 每个测试函数必须导入testing
包,测试函数的基本格式(签名)如下:
func TestName (t *testing.T) { }
测试函数的名字必须以Test
开头,可选的后缀名必须以大写字母开头,举几个例子:
func TestAdd (t *testing.T) { ... }func TestSum (t *testing.T) { ... }func TestLog (t *testing.T) { ... }
其中参数t
用于报告测试失败和附加的日志信息。 testing.T
的拥有的方法如下:
func (c *T) Error(args ...interface {})func (c *T) Errorf(format string , args ...interface {})func (c *T) Fail()func (c *T) FailNow()func (c *T) Failed() bool func (c *T) Fatal(args ...interface {})func (c *T) Fatalf(format string , args ...interface {})func (c *T) Log(args ...interface {})func (c *T) Logf(format string , args ...interface {})func (c *T) Name() string func (t *T) Parallel()func (t *T) Run(name string , f func (t *T) ) bool func (c *T) Skip(args ...interface {})func (c *T) SkipNow()func (c *T) Skipf(format string , args ...interface {})func (c *T) Skipped() bool
测试函数示例 就像细胞是构成我们身体的基本单位,一个软件程序也是由很多单元组件构成的。单元组件可以是函数、结构体、方法和最终用户可能依赖的任意东西。总之我们需要确保这些组件是能够正常运行的。单元测试是一些利用各种方法测试单元组件的程序,它会将结果与预期输出进行比较。
接下来,我们定义一个split
的包,包中定义了一个Split
函数,具体实现如下:
package splitimport "strings" func Split (s, sep string ) (result []string ) { i := strings.Index(s, sep) for i > -1 { result = append (result, s[:i]) s = s[i+1 :] i = strings.Index(s, sep) } result = append (result, s) return }
在当前目录下,我们创建一个split_test.go
的测试文件,并定义一个测试函数如下:
package splitimport ( "reflect" "testing" ) func TestSplit (t *testing.T) { got := Split("a:b:c" , ":" ) want := []string {"a" , "b" , "c" } if !reflect.DeepEqual(want, got) { t.Errorf("expected:%v, got:%v" , want, got) } }
在split
包路径下,执行go test
命令,可以看到输出结果如下:
split $ go test PASS ok github.com/Q1mi/studygo/code_demo/test_demo/split 0.005s
测试组 func TestSplit (t *testing.T) { type test struct { input string sep string want []string } tests := []test{ {input: "a:b:c" , sep: ":" , want: []string {"a" , "b" , "c" }}, {input: "a:b:c" , sep: "," , want: []string {"a:b:c" }}, {input: "abcd" , sep: "bc" , want: []string {"a" , "d" }}, {input: "沙河有沙又有河" , sep: "沙" , want: []string {"河有" , "又有河" }}, } for _, tc := range tests { got := Split(tc.input, tc.sep) if !reflect.DeepEqual(got, tc.want) { t.Errorf("expected:%v, got:%v" , tc.want, got) } } }
测试覆盖率 go test -cover
来查看测试覆盖率
基准测试 基准测试就是在一定的工作负载之下检测程序性能的一种方法。基准测试的基本格式如下:
func BenchmarkName (b *testing.B) { }
基准测试以Benchmark
为前缀,需要一个*testing.B
类型的参数b,基准测试必须要执行b.N
次,这样的测试才有对照性,b.N
的值是系统根据实际情况去调整的,从而保证测试的稳定性。 testing.B
拥有的方法如下:
func (c *B) Error(args ...interface {})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 {})func (c *B) Fatalf(format string , args ...interface {})func (c *B) Log(args ...interface {})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 func (b *B) RunParallel(body func (*PB) )func (b *B) SetBytes(n int64 )func (b *B) SetParallelism(p int )func (c *B) Skip(args ...interface {})func (c *B) SkipNow()func (c *B) Skipf(format string , args ...interface {})func (c *B) Skipped() bool func (b *B) StartTimer()func (b *B) StopTimer()
基准测试示例 func BenchmarkSplit (b *testing.B) { for i := 0 ; i < b.N; i++ { Split("沙河有沙又有河" , "沙" ) } }
基准测试并不会默认执行,需要增加-bench
参数,所以我们通过执行go test -bench=Split
命令执行基准测试,输出结果如下:
split $ go test -bench=Splitgoos: darwin goarch: amd64 pkg: github.com/Q1mi/studygo/code_demo/test_demo/split BenchmarkSplit-8 10000000 203 ns/op PASS ok github.com/Q1mi/studygo/code_demo/test_demo/split 2.255s
性能比较函数 上面的基准测试只能得到给定操作的绝对耗时,但是在很多性能问题是发生在两个不同操作之间的相对耗时,比如同一个函数处理1000个元素的耗时与处理1万甚至100万个元素的耗时的差别是多少?再或者对于同一个任务究竟使用哪种算法性能最佳?我们通常需要对两个不同算法的实现使用相同的输入来进行基准比较测试。
并行测试 func (b *B) RunParallel(body func(*PB))
会以并行的方式执行给定的基准测试。
RunParallel
会创建出多个goroutine
,并将b.N
分配给这些goroutine
执行, 其中goroutine
数量的默认值为GOMAXPROCS
。用户如果想要增加非CPU受限(non-CPU-bound)基准测试的并行性, 那么可以在RunParallel
之前调用SetParallelism
。RunParallel
通常会与-cpu
标志一同使用。
func BenchmarkSplitParallel (b *testing.B) { b.RunParallel(func (pb *testing.PB) { for pb.Next() { Split("沙河有沙又有河" , "沙" ) } }) }
执行一下基准测试:
split $ go test -bench=.goos: darwin goarch: amd64 pkg: github.com/Q1mi/studygo/code_demo/test_demo/split BenchmarkSplit-8 10000000 131 ns/op BenchmarkSplitParallel-8 50000000 36.1 ns/op PASS ok github.com/Q1mi/studygo/code_demo/test_demo/split 3.308s
还可以通过在测试命令后添加-cpu
参数如go test -bench=. -cpu 1
来指定使用的CPU数量。