go语言单元测试

Go本身提供了一套轻量级的测试框架.符合规则的测试代码会在运行测试时被自动识别并执行.单元测试源文件的命名规则如平衡点:在需要测试的包下面创建以”_test”结尾的go文件,开如[^.]*_test.go

Go单元测试函数分为两在类.功能测试函数性能测试函数,分别以TestBenchmark为函数名前缀并以*testing.T 和 *testing.B 为单一参数的函数。

func TestAdd1(t *testing.T)
func BenchmarkAdd1(t *testing.T)

测试工具会根据函数中的实际执行动作得到不同的测试结果。

功能测试函数会根据测试代码执行过程中是否发生错误来反馈结果;
性能测试函数仅仅打印出来测试所花费时间,用来判断程序性能;

准备

新建一个文件,命名为 go_test.go

package go_test

import "testing"

func Add(a, b int) int {
return a + b
}

功能测试

在go_test.go文件里添加以下代码

func TestAdd1(t *testing.T) {
r := Add(1, 2)
if r != 2 { //这里为了测试,写成2
t.Errorf("Add(1,2) failed. Got %d, expected 3.", r)
}
}

执行功能单元测试非常的简单,直接执行

go test

输出以下内容

--- FAIL: TestAdd1 (0.00s)
go_test.go:12: Add(1,2) failed. Got 3, expected 3.
FAIL
exit status 1
FAIL gotest 0.130s

可以看到输出具体出错的位置为第12行,控制台并打印出了用户定义的错误信息。最后的FAIL表示测试未通过,如果为 ok 的话,则为通过。我们修正问题后再次执行结果为

PASS
ok gotest 0.146s

如果你在同一个文件里添加了多个测试函数的话,直接执行 go test,发现只要有一个函数未通过,则打印结果只有一个FAIL,对用户不太友好,这时可以使用 -v 参数 go test -v,表示输出每个功能函数的详情的信息。

=== RUN TestAdd1
--- FAIL: TestAdd1 (0.00s)
go_test.go:12: Add(1,2) failed. Got 3, expected 3.
FAIL
exit status 1
FAIL gotest 0.208s

=== RUN 行表示执行的函数
— FAIL 行表示上行执行的函数结果,如果测试通过则为 PASS
第三行 表示FAIL后的打印错误信息,如果函数测试通过是没有这一行的
第四行 表示本次测试的结果,只要有一个函数未通过,就为FAIL
如果有多个函数的话,则重复打印前两行。

*testing.T 中有一个 t.Errorf() 函数打印一些错误信息后中止测试。

说明:

对于 go test 命令如果一个文件中有一个方法未通过,则直接输出 FAIL,如果想知道具体哪个方法未通过,需要使用 -v 参数来定位。

性能测试

上面我们介绍了功能测试,再来看下性能测试。可以与功能测试在同一个文件中,默认情况下go test 并不会执行Benchmark测试,必须使用参数 ” -bench <pattern>” 指定性能测试函数。在go_test.go文件中添加以下函数。(记得先修正功能测试未通过的问题)

func BenchmarkAdd1(b *testing.B) {
for i := 0; i < b.N; i++ {
    Add(1, 2)
}
}

我们可以看出,性能测试与功能测试代码相比,最大的区别在于代码里的这个for循环,循环b.N次.写这个for循环的原因是为了能够让测试运行足够长的时间便于进行平均运行时间的计算.如果测试代码中一些准备工作的时间太长,我们也可以这样处理以明确排除这些准备工作所花费时间对于性能测试的时间影响:

func BenchmarkAdd1(b *testing.B) {
    b.Stoptimer()
    DoPreparation()
    b.StartTimer()

    for i := 0; i < b.N; i++ { //b.N,测试循环次数 
Add(1, 2)
}
}

性能单元的执行与功能测试一样简单,只不过调用时需要-test.bench参数而已,具体代码如下所示:

$go test -bench=".*"
goos: darwin
goarch: amd64
pkg: gotest
BenchmarkAdd1-4 1000000000 0.367 ns/op
PASS
ok gotest 0.698s

结果解释

goos: darwin 表示平台系统,这里是mac
goarch: amd64 表示64位系统
pkg: 表示包名
BenchmarkAdd1-4: 表示测试的函数名称,-后面的数字表示cpu个数
1000000000: 表示共执行了 1000000000 次
0.367 ns/op: 表示每次执行耗时0.267纳秒,这里肯定是越小越好

另外对性能测试还支持一些其它的参数

  • benchmem:输出内存分配统计
  • benchtime:指定测试时间
  • cpu:指定GOMAXPROCS
  • timeout:超时限制
$ go test -v -bench=. -cpu=2 -benchtime="3s" -timeout="5s" -benchmem
=== RUN TestAdd1
--- PASS: TestAdd1 (0.00s)
goos: darwin
goarch: amd64
pkg: gotest
BenchmarkAdd1-2 1000000000 0.325 ns/op 0 B/op 0 allocs/op
PASS
ok gotest 0.460s

结果解释

BenchmarkAdd1-2: 测试函数名称,后面的2表示2个cpu线程
0 B/op:表示每次执行分配的内存(字节)
0 allocs/op:表示每次执行分配了多少次对象

从上面可以看出对于功能测试的命令为 go test,而对于性能测试则是 go test-test.bench add.go (在调用的时候添加 -test.bench参数而已)

 go test 的两种运行模式:

目录模式:当没有指定包参数时,如go test 或者 go test -v, 则在用户当前所在目录查找单元测试文件并执行,最后打印结果。这种模式下缓存功能是禁用的。

包列表板式:显示指定了包名,如(‘go test math’, ‘go test ./…’, and even ‘go test .’)

go test 的用法很灵活,如 -run 参数
 
-run regex:指定要执行的测试函数
 
go test -run ” # Run all tests.
go test -run Foo # Run top-level tests matching “Foo”, such as “TestFooBar”.
go test -run Foo/A= # For top-level tests matching “Foo”, run subtests matching “A=”.
go test -run /A=1 # For all top-level tests, run subtests matching “A=1”.

除了对项目中的所有文件进行单元测试外,还可以对指定的包进行单元测试,如对于整个 simplemath 包进行单元测试:

$go test simplemath
PASS
ok simplemath 0.013s
对于更详情的用法可以使用 go help test 了解。
 
对于 testing 包中的一些常用函数可以在文件中/src/testing/testing.go 找到。
 

性能分析工具pprof 

对于性能测试结果只是程序的最终性能报告,但对于系统中性能瓶颈在哪个地方却无法给出,所以就引出另一个问题,如何对性能进行精确定位。这就需要用到go tool了

这里可以使用 go test -v -bench=”.*” -cpuprofile=cpu.prof -c` (参考:http://www.cnblogs.com/yjf512/archive/2013/01/18/2865915.html)

cpuprofile是表示生成的cpu profile文件

-c是生成可执行的二进制文件,这个是生成状态图必须的,它会在本目录下生成可执行文件mymysql.test

然后使用go tool pprof工具

go tool pprof mymysql.test cpu.prof

==========================

文件mymath.go

package mymath

func Add(a, b int) int {
	return a + b
}
func Max(a, b int) (ret int) {
	ret = a
	if b < a {
		ret = b
	}
	return
}

测试文件 mymath_test.go(所有测试文件都必须以*_test.go结尾)

package mymath_test

import (
	"mymath"
	"testing"
)

type mathTest struct {
	a, b, ret int
}

var addTest = []mathTest{
	mathTest{4, 6, 10},
	mathTest{5, 6, 11},
	mathTest{2, -6, -4},
}

var maxTest = []mathTest{
	mathTest{3, 5, 5},
	mathTest{-3, 5, 5},
	mathTest{-3, -5, -3},
}

func TestAdd(t *testing.T) {
	for _, v := range addTest {
		ret := mymath.Add(v.a, v.b)
		if ret != v.ret {
			t.Errorf("%d add %d, want %d, but get %d", v.a, v.b, v.ret, ret)
		}
	}
}
func TestMax(t *testing.T) {
	for _, v := range maxTest {
		ret := mymath.Max(v.a, v.b)
		if ret != v.ret {
			t.Errorf("the max number between %d and %d is want %d, but get %d", v.a, v.b, v.ret, ret)
		}
	}
}

执行 go test

PASS
ok  	mymath	0.246s

4 thoughts on “go语言单元测试

  1. Pingback: Digital Transformation consultants

  2. Pingback: hotwire after midnight

  3. Pingback: replica panarai watches

  4. Pingback: Benefits of intelligent automation

Comments are closed.