【Golang】测试包:testing

虽说大部分公司都不需要做单元测试,但是这方面的内容还是需要了解的。也有小伙伴问我为什么要去看文档的包里有什么东西,面向百度开发不就好了吗?个人的理解是阅读一遍包不是为了记住所有的函数和方法,而是为了知道这个包里都有什么,可以干什么,在遇到相关问题的时候可以针对的查询而不是复制粘贴。

package testing

go中想要写测试用例的话需要用到 testing 包,可用于 函数测试基准测试 两个地方

函数测试

testing 提供对 Go 包的自动化测试的支持。通过 go test 命令,能够自动执行如下形式的任何函数:

func TestXxx(*testing.T)

其中 Xxx 可以是任何字母数字字符串(但第一个字母不能是 [a-z]),用于识别测试例程。

在这些函数中,使用 Error, Fail 或相关方法来发出失败信号。

要编写一个新的测试套件,需要创建一个名称以 _test.go 结尾的文件,该文件包含 TestXxx 函数,如上所述。 将该文件放在与被测试的包相同的包中。该文件将被排除在正常的程序包之外,但在运行 “go test” 命令时将被包含。 有关详细信息,请运行 “go help test” 和 “go help testflag” 了解。

从文档中可以知道,要编写一个测试的文件需要以 xxx_test.go 格式命名文件,文件中需要包括 TestXxx() 的函数

文件必须以 _test.go 结尾,测试函数必须为以 Test 开头的大驼峰命名。

基准测试

如下形式的函数:

func BenchmarkXxx(*testing.B)

被认为是基准测试,通过 "go test" 命令,加上 -bench flag 来执行。多个基准测试按照顺序运行。

testing flags 的详细描述, 参见 https://github.com/golang/go/blob/master/cmd/go/#hdr-Description_of_testing_flags.

基准测试函数样例看起来如下所示:

func BenchmarkHello(b *testing.B) {
    for i := 0; i < b.N; i++ {
        fmt.Sprintf("hello")
    }
}

基准函数会运行目标代码 b.N 次。在基准执行期间,会调整 b.N 直到基准测试函数持续足够长的时间。输出

BenchmarkHello    10000000    282 ns/op
意味着循环执行了 10000000 次,每次循环花费 282 纳秒(ns)。

如果在运行前基准测试需要一些耗时的配置,则可以先重置定时器:

func BenchmarkBigLen(b *testing.B) {
    big := NewBig()
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        big.Len()
    }
}

如果基准测试需要在并行设置中测试性能,则可以使用 RunParallel 辅助函数; 这样的基准测试一般与 go test -cpu 标志一起使用:

func BenchmarkTemplateParallel(b *testing.B) {
    templ := template.Must(template.New("test").Parse("Hello, {{.}}!"))
    b.RunParallel(func(pb *testing.PB) {
        var buf bytes.Buffer
        for pb.Next() {
            buf.Reset()
            templ.Execute(&buf, "World")
        }
    })
}
  1. 基准测试需要以 BenchmarkXxx() 命名函数
  2. 可以使用 b.ResetTimer() 来重置基准测试耗时用来跳过等待和预备的时间

动手写一份测试

测试对象

首先写一个测试对象,比如需要自己写一个反转字符串的函数:

package reverse

func ReverseBySlice(s string) string {
	rs := []rune(s)
	var ret []rune
	for i := len(rs); i > 0; i -- {
		ret = append(ret, rs[i-1])
	}
	return string(ret)
}

func ReverseByString(s string) string {
	rs := []rune(s)
	var ret string
	for i := len(rs); i >= 0; i -- {
		ret += string(rs[i:i+1])
	}
	return ret
}

函数测试

每个测试函数必须导入testing包,测试函数的基本格式(签名)如下:

func TestName(t *testing.T){
    // ...
}

测试函数的名字必须以Test开头,可选的后缀名必须以大写字母开头,以上面的测试对象举例:

func TestReverseBySlice(t *testing.T) {}

func TestReverseByString(t *testing.T) {}

其中参数t用于报告测试失败和附加的日志信息。

testing
type T struct {
    // contains filtered or unexported fields
}

T 是传递给测试函数的一种类型,它用于管理测试状态并支持格式化测试日志。测试日志会在执行测试的过程中不断累积, 并在测试完成时转储至标准输出。

当一个测试的测试函数返回时, 又或者当一个测试函数调用 FailNow 、 Fatal 、 Fatalf 、 SkipNow 、 Skip 或者 Skipf 中的任意一个时, 该测试即宣告结束。 跟 Parallel 方法一样, 以上提到的这些方法只能在运行测试函数的 goroutine 中调用。

至于其他报告方法, 比如 Log 以及 Error 的变种, 则可以在多个 goroutine 中同时进行调用。

下面是T的内置方法

  1. func (c *T) Error(args ...interface{})

调用 Error 相当于在调用 Log 之后调用 Fail 。

  1. func (c *T) Errorf(format string, args ...interface{})

调用 Errorf 相当于在调用 Logf 之后调用 Fail 。

  1. func (c *T) Fail()

将当前测试标识为失败,但是仍继续执行该测试。

  1. func (c *T) FailNow()

将当前测试标识为失败并停止执行该测试,在此之后,测试过程将在下一个测试或者下一个基准测试中继续。 FailNow 必须在运行测试函数或者基准测试函数的 goroutine 中调用,而不能在测试期间创建的 goroutine 中调用。调用 FailNow 不会导致其他 goroutine 停止。

  1. func (c *T) Failed() bool

Failed 用于报告测试函数是否已失败。

  1. func (c *T) Fatal(args ...interface{})

调用 Fatal 相当于在调用 Log 之后调用 FailNow 。

  1. func (c *T) Fatalf(format string, args ...interface{})

调用 Fatalf 相当于在调用 Logf 之后调用 FailNow 。

  1. func (c *T) Log(args ...interface{})

Log 使用与 Println 相同的格式化语法对它的参数进行格式化,然后将格式化后的文本记录到错误日志里面:

  1. 对于测试来说,格式化文本只会在测试失败或者设置了 -test.v 标志的情况下被打印出来;
  2. 对于基准测试来说,为了避免 -test.v 标志的值对测试的性能产生影响, 格式化文本总会被打印出来。
  1. func (c *T) Logf(format string, args ...interface{})

Log 使用与 Printf 相同的格式化语法对它的参数进行格式化,然后将格式化后的文本记录到错误日志里面。 如果输入的格式化文本最末尾没有出现新行,那么将一个新行添加到格式化后的文本末尾。

  1. 对于测试来说,Logf 产生的格式化文本只会在测试失败或者设置了 -test.v 标志的情况下被打印出来;
  2. 对于基准测试来说,为了避免 -test.v 标志的值对测试的性能产生影响,Logf 产生的格式化文本总会被打印出来。
  1. func (c *T) Name() string

返回正在运行的测试或基准测试的名字。

  1. func (t *T) Parallel()

Parallel 用于表示当前测试只会与其他带有 Parallel 方法的测试并行进行测试。

  1. func (t *T) Run(name string, f func(t *T)) bool

执行名字为 name 的子测试 f ,并报告 f 在执行过程中是否出现了任何失败。Run 将一直阻塞直到 f 的所有并行测试执行完毕。 Run 可以在多个 goroutine 里面同时进行调用,但这些调用必须发生在 t 的外层测试函数(outer test function)返回之前。

  1. func (c *T) Skip(args ...interface{})

调用 Skip 相当于在调用 Log 之后调用 SkipNow 。

  1. func (c *T) SkipNow()

将当前测试标识为“被跳过”并停止执行该测试。 如果一个测试在失败(参考 Error、Errorf 和 Fail)之后被跳过了, 那么它还是会被判断为是“失败的”。 在停止当前测试之后,测试过程将在下一个测试或者下一个基准测试中继续,具体请参考 FailNow 。 SkipNow 必须在运行测试的 goroutine 中进行调用,而不能在测试期间创建的 goroutine 中调用。 调用 SkipNow 不会导致其他 goroutine 停止。

  1. `func (c *T) Skipf(format string, args ...interface{})·

调用 Skipf 相当于在调用 Logf 之后调用 SkipNow 。

  1. func (c *T) Skipped() bool

Skipped 用于报告测试函数是否已被跳过。

编写一个简单的函数测试

一个简单的函数测试(单元测试)的意义在于判断测试组件(函数)是否可以正常运行,一般来说需要先设置参数,预期返回值,然后将预期返回值的类型或者实际值与实际返回的值做对比。

首先看看目录结构,根据上面的内容,需要创建一个xxxx_test.go的文件

2020/03/周四  14:34    <DIR>          .
2020/03/周四  14:34    <DIR>          ..
2020/03/周四  14:15               328 reverse.go
2020/03/周四  14:34             1,038 reverse_test.go

在 reverse_test.go中写入如下测试用例

package reverse

import (
	"reflect"
	"testing"
)

func TestReverseBySlice(t *testing.T) {
	param := "1234567"           // 参数
	want := "7654321"            // 预期返回
	ret := ReverseBySlice(param) // 实际返回
	if !reflect.DeepEqual(ret, want) { // 值对比,这里是字符串可以直接用 = ,但是如果是map或者slice类的指针用 = 就不行了,还是使用反射深拷贝比较稳妥
		t.Errorf("测试错误,参数:%#v,预期:%#v,返回:%#v", param, want, ret)
	} else {
		t.Logf("测试用例[%v]通过", t.Name())
	}
}

func TestReverseByString(t *testing.T) {
	param := "一二三四五六七"
	want := "七六五四三二一"
	ret := ReverseBySlice(param)
	if !reflect.DeepEqual(ret, want) {
		t.Errorf("测试错误,参数:%#v,预期:%#v,返回:%#v", param, want, ret)
	} else {
		t.Logf("测试用例[%v]通过", t.Name())
	}
}

func TestReverseBySlice2(t *testing.T) { // 模拟一个测试失败的场景
	param := "1234567"
	want := "76543211"
	ret := ReverseBySlice(param)
	if !reflect.DeepEqual(ret, want) {
		t.Errorf("测试错误,参数:%#v,预期:%#v,返回:%#v", param, want, ret)
	} else {
		t.Logf("测试用例[%v]通过", t.Name())
	}
}
使用 go test 来开始测试
λ go test
--- FAIL: TestReverseBySlice2 (0.00s)
    reverse_test.go:35: 测试错误,参数:"1234567",预期:"76543211",返回:"7654321"
FAIL
exit status 1
FAIL    study/test      0.173s
根据上面的文档,想要在命令行输出非失败日志内容,需要加上参数 -v
λ go test -v
=== RUN   TestReverseBySlice
    TestReverseBySlice: reverse_test.go:15: 测试用例[TestReverseBySlice]通过
--- PASS: TestReverseBySlice (0.00s)
=== RUN   TestReverseByString
    TestReverseByString: reverse_test.go:26: 测试用例[TestReverseByString]通过
--- PASS: TestReverseByString (0.00s)
=== RUN   TestReverseBySlice2
    TestReverseBySlice2: reverse_test.go:35: 测试错误,参数:"1234567",预期:"76543211",返回:"7654321"
--- FAIL: TestReverseBySlice2 (0.00s)
FAIL
exit status 1
FAIL    study/test      0.188s
可以增加 -run=正则 来测试可匹配的指定用例:
C:\project\go\src\study\test
λ go test -v -run=TestReverseBySlice$
=== RUN   TestReverseBySlice
    TestReverseBySlice: reverse_test.go:15: 测试用例[TestReverseBySlice]通过
--- PASS: TestReverseBySlice (0.00s)
PASS
ok      study/test      0.178s

C:\project\go\src\study\test
λ go test -v -run=TestReverseBySlice
=== RUN   TestReverseBySlice
    TestReverseBySlice: reverse_test.go:15: 测试用例[TestReverseBySlice]通过
--- PASS: TestReverseBySlice (0.00s)
=== RUN   TestReverseBySlice2
    TestReverseBySlice2: reverse_test.go:35: 测试错误,参数:"1234567",预期:"76543211",返回:"7654321"
--- FAIL: TestReverseBySlice2 (0.00s)
FAIL
exit status 1
FAIL    study/test      0.175s
测试组

如果想要对一个方法测试多个用例,可以使用测试组。如上面的测试对象反转字符串,如果想同时测试中文,英文,中英混编,可以这样:

func TestReverseBySlice(t *testing.T) {
	testCase := []struct {
		param string
		want  string
	}{
		{param: "abcdefg", want: "gfedcba"},
		{param: "一二三四", want: "四三二一"},
		{param: "a一b二c三", want: "三c二b一a的"},
	}

	for _, v := range testCase {
		ret := ReverseBySlice(v.param)
		if !reflect.DeepEqual(v.want, ret) {
			t.Errorf("测试错误,参数:%#v,预期:%#v,返回:%#v", v.param, v.want, ret)
		} else {
			t.Logf("测试通过")
		}
	}
}
λ go test -v
=== RUN   TestReverseBySlice
    TestReverseBySlice: reverse_test.go:24: 测试通过
    TestReverseBySlice: reverse_test.go:24: 测试通过
    TestReverseBySlice: reverse_test.go:21: asdad
    TestReverseBySlice: reverse_test.go:22: 测试错误,参数:"a一b二c三",预期:"三c二b一a的",返回:"三c二b一a"
--- FAIL: TestReverseBySlice (0.00s)
FAIL
exit status 1
FAIL    study/test      0.175s
子测试

有的时候测试用例太多,形成测试组后出错不好定位出错的子测试。这个时候我们可以使用 map 结构来定义子测试,用 key 作为别名来进行子测试:

func TestReverseBySlice(t *testing.T) {
	type testCase struct {
		param string
		want  string
	}

	testCases := map[string]testCase{
		"en":    {param: "abcdefg", want: "gfedcba"},
		"ch":    {param: "一二三四", want: "四三二一"},
		"en+ch": {param: "a一b二c三", want: "三c二b一a的"},
	}

	for k, v := range testCases {
		t.Run(k, func(t *testing.T) { // 使用t.Run()执行子测试
			ret := ReverseBySlice(v.param)
			if !reflect.DeepEqual(v.want, ret) {
				t.Errorf("测试错误,参数:%#v,预期:%#v,返回:%#v", v.param, v.want, ret)
			} else {
				t.Logf("测试通过")
			}
		})
	}
}
λ go test -v
=== RUN   TestReverseBySlice
=== RUN   TestReverseBySlice/en
    TestReverseBySlice/en: reverse_test.go:26: 测试通过
=== RUN   TestReverseBySlice/ch
    TestReverseBySlice/ch: reverse_test.go:26: 测试通过
=== RUN   TestReverseBySlice/en+ch
    TestReverseBySlice/en+ch: reverse_test.go:24: 测试错误,参数:"a一b二c三",预期:"三c二b一a的",返回:"三c二b一a"
--- FAIL: TestReverseBySlice (0.00s)
    --- PASS: TestReverseBySlice/en (0.00s)
    --- PASS: TestReverseBySlice/ch (0.00s)
    --- FAIL: TestReverseBySlice/en+ch (0.00s)
FAIL
exit status 1
FAIL    study/test      0.189s

当然,也支持参数 -run 来执行指定子测试,例如像执行 TestReverseBySlice/ch

λ go test -v -run=^TestReverseBySlice/en$
=== RUN   TestReverseBySlice
=== RUN   TestReverseBySlice/en
    TestReverseBySlice/en: reverse_test.go:26: 测试通过
--- PASS: TestReverseBySlice (0.00s)
    --- PASS: TestReverseBySlice/en (0.00s)
PASS
ok      study/test      0.175s
测试覆盖率

测试覆盖率是代码被测试套件覆盖的百分比。通常我们使用的都是语句的覆盖率,也就是在测试中至少被运行一次的代码占总代码的比例。

Go提供内置功能来检查你的代码覆盖率。我们可以使用go test -cover来查看测试覆盖率

λ go test -cover
PASS
coverage: 50.0% of statements
ok      study/test      0.171s

从上面的结果可以看到我们的测试用例覆盖了50%的代码。

Go还提供了一个额外的-coverprofile参数,用来将覆盖率相关的记录信息输出到一个文件。例如:

λ go test -cover -coverprofile=cover.out
PASS
coverage: 50.0% of statements
ok      study/test      0.184s

上面的命令会将覆盖率相关的信息输出到当前文件夹下面的cover.out文件中,然后我们执行go tool cover -html=cover.out,使用cover工具来处理生成的记录信息,该命令会打开本地的浏览器窗口生成一个HTML报告。

基准测试

基准测试就是在一定的工作负载之下检测程序性能的一种方法。

由上文可以看到,基准测试定义函数的方法是由 Benchmark 开头的大驼峰命名,参数为 *testing.B

func BenchmarkXxx(*testing.B)
type B
type B struct {
    N int
    // contains filtered or unexported fields
}

B 是传递给基准测试函数的一种类型,它用于管理基准测试的计时行为,并指示应该迭代地运行测试多少次。

一个基准测试在它的基准测试函数返回时,又或者在它的基准测试函数调用 FailNow、Fatal、Fatalf、SkipNow、Skip 或者 Skipf 中的任意一个方法时,测试即宣告结束。至于其他报告方法,比如 Log 和 Error 的变种,则可以在其他 goroutine 中同时进行调用。

跟单元测试一样,基准测试会在执行的过程中积累日志,并在测试完毕时将日志转储到标准错误。但跟单元测试不一样的是,为了避免基准测试的结果受到日志打印操作的影响,基准测试总是会把日志打印出来。

基准测试主要是用来测试程序运行的时间消耗和内存消耗的。他拥有和 testing.T 一样的打印日志的方法,同时也拥有两个额外的统计开关方法:

  1. func (b *B) StartTimer()

开始对测试进行计时。 这个函数在基准测试开始时会自动被调用, 它也可以在调用 StopTimer 之后恢复进行计时。

  1. func (b *B) StopTimer()

停止对测试进行计时。 当你需要执行一些复杂的初始化操作, 并且你不想对这些操作进行测量时, 就可以使用这个方法来暂时地停止计时。

一个示例
func BenchmarkReverseBySlice(b *testing.B) {
	for i := 0; i < b.N; i++ {
		ReverseBySlice("1234567890一二三四五六七八九十")
	}
}

基准函数会运行目标代码 b.N 次。在基准执行期间,会调整 b.N 直到基准测试函数持续足够长的时间。

λ go test -bench=ReverseBySlice -v
goos: windows
goarch: amd64
pkg: study/test
BenchmarkReverseBySlice
BenchmarkReverseBySlice-4        2537934               466 ns/op
PASS
ok      study/test      1.830s
  1. BenchmarkReverseBySlice 为基准测试对象
  2. BenchmarkReverseBySlice-4 这个4代表GOMAXPROCS的值,每一个代表使用一个核心,在协程中说过,这个对于并发基准测试很重要。
  3. 2537934为运行次数
  4. 466 ns/op 为每次运行需要的时间

这里为,使用4个核心跑了2537934次,平均每次466纳秒。

增加参数 -benchmem 来查看内存占用和分配

λ go test -bench=ReverseBySlice -benchmem
goos: windows
goarch: amd64
pkg: study/test
BenchmarkReverseBySlice-4        2590981               465 ns/op             296 B/op          6 allocs/op
PASS
ok      study/test      1.850s

296 B/op 为每次执行消耗的内存数,6 allocs/op为每次运行申请内存的次数

针对内存优化的案例

首先拿出之前的测试对象:

func ReverseBySlice(s string) string {
	rs := []rune(s)
	var ret []rune
	for i := len(rs); i > 0; i -- {
		ret = append(ret, rs[i-1])
	}
	return string(ret)
}

这是一个很简单的反转字符串的函数,怎么会申请6次内存呢?查看代码也就两个地方可能存在内存申请:

  1. rs := []rune(s) 创建变量rs申请了一次
  2. var ret []rune 创建变量ret申请了一次

回头查看一下slice切片动态伸缩的内容就可以知道了,这里因为没有设置切片的容量,在 append 的时候会动态的对切片进行扩容,扩容的动作是很繁琐的,包括查看现在的容量,新旧容量对比,复制旧数据到新切片,内存对齐等。那么这里对测试对象优化一下,在创建切片的时候给他分配好容量再试试

func ReverseBySlice(s string) string {
	rs := []rune(s)
	ret := make([]rune,0,len(rs))
	for i := len(rs); i > 0; i -- {
		ret = append(ret, rs[i-1])
	}
	return string(ret)
}
λ go test -bench=ReverseBySlice -benchmem
goos: windows
goarch: amd64
pkg: study/test
BenchmarkReverseBySlice-4        3823582               311 ns/op             128 B/op          2 allocs/op
PASS
ok      study/test      1.681s

新旧对比:

旧: 465 ns/op             296 B/op          6 allocs/op
新: 311 ns/op             128 B/op          2 allocs/op

可以看到,优化了一下后,内存占用仅为一半,内存申请只用了2次,由这两点带来的是节省了1/3的执行时间,加速了代码效率。

性能比较

有的时候,我们需要对不同参数的相同测试对象进行性能比较,评估在不同情况下的性能消耗,如在一个列表接口中,需要评估每页5,10,20,100个数据接口的实际表现等。或者一个列表接口的多表查询,进行子查询和连表查询的效率等。这里以上面的反转字符串为例,写了2个测试对象:

func ReverseBySlice(s string) string { // 这个是已经优化过的
	rs := []rune(s)
	ret := make([]rune,0,len(rs))
	for i := len(rs); i > 0; i -- {
		ret = append(ret, rs[i-1])
	}
	return string(ret)
}

func ReverseByString(s string) string {
	rs := []rune(s)
	var ret string
	for i := len(rs); i >= 0; i -- {
		ret += string(rs[i:i+1])
	}
	return ret
}

测试用例

func BenchmarkReverseBySlice(b *testing.B) {
	for i := 0; i < b.N; i++ {
		ReverseBySlice("1234567890")
	}
}

func BenchmarkReverseByString(b *testing.B) {
	for i := 0; i < b.N; i++ {
		ReverseBySlice("1234567890")
	}
}

func BenchmarkReverseBySlice1(b *testing.B) {
	for i := 0; i < b.N; i++ {
		ReverseBySlice("1234567890一二三四五六七八九十")
	}
}

func BenchmarkReverseByString1(b *testing.B) {
	for i := 0; i < b.N; i++ {
		ReverseBySlice("1234567890一二三四五六七八九十")
	}
}

测试

λ go test -bench=Reverse. -benchmem
goos: windows
goarch: amd64
pkg: study/test
BenchmarkReverseBySlice-4        9547609               126 ns/op              64 B/op          2 allocs/op
BenchmarkReverseByString-4       9541809               126 ns/op              64 B/op          2 allocs/op
BenchmarkReverseBySlice1-4       3867416               311 ns/op             128 B/op          2 allocs/op
BenchmarkReverseByString1-4      3846948               313 ns/op             128 B/op          2 allocs/op
PASS
ok      study/test      5.893s

从测试可以看出来,操作字符串还是稍微比切片快一点点的,不过都比未优化过的切片动态扩容速度快

重置时间和提前结束

在开发中,有的测试需要先进行准备动作,例如准备数据,连接数据库等等。在测试完后需要销毁数据或断开连接,但是这些行为在实际测试中不希望进入基准测试计时,这个时候就可以使用 b.ResetTimerb.StopTimer 来控制测试的开始和结束时间:

func BenchmarkReverseByString(b *testing.B) {
	time.Sleep(time.Millisecond * 200)
	for i := 0; i < b.N; i++ {
		ReverseBySlice("1234567890")
	}
	time.Sleep(time.Millisecond * 200)
}

func BenchmarkReverseByString1(b *testing.B) {
	time.Sleep(time.Millisecond * 200)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		ReverseBySlice("1234567890")
	}
	b.StopTimer()
	time.Sleep(time.Millisecond * 200)
}
λ go test -bench=Reverse. -benchmem
goos: windows
goarch: amd64
pkg: study/test
BenchmarkReverseByString-4       5498692               198 ns/op              64 B/op          2 allocs/op
BenchmarkReverseByString1-4      9334227               126 ns/op              64 B/op          2 allocs/op
PASS
ok      study/test      12.534s
并行测试
type PB struct {
    // contains filtered or unexported fields
}

PB 被 RunParallel 使用来运行并行基准测试。

func (pb *PB) Next() bool Next 判断是否有更多的迭代要执行

使用 func (b *B) RunParallel(body func(*PB) 来进行并行测试

RunParallel 会创建出多个协程,并将b.N分配给这些协程执行, 其中协程数量的默认值为GOMAXPROCS。用户如果想要增加非CPU受限(non-CPU-bound)基准测试的并行性, 那么可以在RunParallel之前调用SetParallelismRunParallel通常会与-cpu标志一同使用。

func BenchmarkReverseByString(b *testing.B) {
	for i := 0; i < b.N; i++ {
		ReverseBySlice("1234567890")
	}
}

func BenchmarkReverseByStringWithParallel(b *testing.B) {
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			ReverseBySlice("1234567890")
		}
	})
}
λ go test -bench=Reverse. -benchmem
goos: windows
goarch: amd64
pkg: study/test
BenchmarkReverseByString-4                       9474129               129 ns/op              64 B/op          2 allocs/op
BenchmarkReverseByStringWithParallel-4          31663605                40.3 ns/op            64 B/op          2 allocs/op
PASS
ok      study/test      2.845s

还可以通过在测试命令后添加-cpu参数如go test -bench=. -cpu 1来指定使用的CPU数量。

λ go test -bench=Reverse. -benchmem -cpu=2
goos: windows
goarch: amd64
pkg: study/test
BenchmarkReverseByString-2                       9540133               125 ns/op              64 B/op          2 allocs/op
BenchmarkReverseByStringWithParallel-2          17694260                66.4 ns/op            64 B/op          2 allocs/op
PASS
ok      study/test      2.736s

小结

golang-testing.xmind

程序幼儿员-龚学鹏
请先登录后发表评论
  • latest comments
  • 总共0条评论