单元测试 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数量。