go 语言 test 测试工具的标记 flag

cpu 标记

用来设置运行时,P的最大数量,即GOMAXPROCS的值。可以设置一组值,在运行测试时,P被设置为不同的值分别运行测试函数。

最大P数量代表着 go 运行时系统同时运行goroutine的能力,可以被视为最大逻辑CPU的数量,在默认情况下,最大 P 数量就等于当前计算机 CPU 核心的实际数量,最大P可以大于或小于实际CPU的核心数量。

通过设置-cpu的值,就可以来模拟程序在不同CPU数量下的表现。设置-cpu=2,4,8

go test -cpu=2,4,8 -bench=. gott/hello
goos: darwin
goarch: amd64
pkg: gott/hello
BenchmarkHello-2         5081475               228.6 ns/op
BenchmarkHello-4         5190867               215.8 ns/op
BenchmarkHello-8         5256798               212.6 ns/op
PASS
ok      gott/hello      4.129s

性能测试函数BenchmarkHello被执行了3次,最大P数量分别为2,4,8

如果没有指定-cpu参数,测试运行时使用默认的最大P的数量,这个数量就等于当前CPU实际的核心数(比如你的CPU是4核,那么这个数量就是4)

go test -bench=.  gott/hello
goos: darwin
goarch: amd64
pkg: gott/hello
BenchmarkHello-4         4670545               234.7 ns/op
PASS
ok      gott/hello      1.381s

BenchmarkHello-4 表明默认P的值为4,即cpu是4核

count 标记

设置重复执行测试函数的次数

性能函数总的执行次数=-cpu标记的值中正整数的个数 x -count标记的值 x 探索式执行中测试函数的实际执行次数=2 * 2 * 5 =20

go test -count=2 -bench=. -cpu=2,4  gott/hello
goos: darwin
goarch: amd64
pkg: gott/hello
BenchmarkHello-2         5184015               224.1 ns/op
--- BENCH: BenchmarkHello-2
    hello_test.go:30: NNNNN: 1
    hello_test.go:30: NNNNN: 100
    hello_test.go:30: NNNNN: 10000
    hello_test.go:30: NNNNN: 1000000
    hello_test.go:30: NNNNN: 5184015
BenchmarkHello-2         5676196               214.5 ns/op
--- BENCH: BenchmarkHello-2
    hello_test.go:30: NNNNN: 1
    hello_test.go:30: NNNNN: 100
    hello_test.go:30: NNNNN: 10000
    hello_test.go:30: NNNNN: 1000000
    hello_test.go:30: NNNNN: 4618954
    hello_test.go:30: NNNNN: 5676196
BenchmarkHello-4         5304111               213.8 ns/op
--- BENCH: BenchmarkHello-4
    hello_test.go:30: NNNNN: 1
    hello_test.go:30: NNNNN: 100
    hello_test.go:30: NNNNN: 10000
    hello_test.go:30: NNNNN: 1000000
    hello_test.go:30: NNNNN: 5304111
BenchmarkHello-4         5286376               212.4 ns/op
--- BENCH: BenchmarkHello-4
    hello_test.go:30: NNNNN: 1
    hello_test.go:30: NNNNN: 100
    hello_test.go:30: NNNNN: 10000
    hello_test.go:30: NNNNN: 1000000
    hello_test.go:30: NNNNN: 5286376
PASS
ok      gott/hello      6.601s

功能函数总的执行次数=-cpu标记的值中正整数的个数 x -count标记的值=3 x 2 = 6

go test -v -count=2  -cpu=2,4,8  gott/hello
=== RUN   TestHello
    hello_test.go:17: number of runtime.GOMAXPROCS: 2
=== RUN   TestHello
    hello_test.go:17: number of runtime.GOMAXPROCS: 2
=== RUN   TestHello
    hello_test.go:17: number of runtime.GOMAXPROCS: 4
=== RUN   TestHello
    hello_test.go:17: number of runtime.GOMAXPROCS: 4
=== RUN   TestHello
    hello_test.go:17: number of runtime.GOMAXPROCS: 8
=== RUN   TestHello
    hello_test.go:17: number of runtime.GOMAXPROCS: 8
ok      gott/hello      0.009s

对于功能测试,并不需要重复的执行多次,只需要执行一次即可,所以可以把-cpu的数值设置为1,-count的值不用设置,使用默认值为1就可以。功能测试关注的重点是验证逻辑是否正确,而不是程序的性能

go test -v   -cpu=1 gott/hello
=== RUN   TestHello
    hello_test.go:17: number of runtime.GOMAXPROCS: 1
--- PASS: TestHello (0.00s)
PASS
ok      gott/hello      0.008s

Parallel 标记

-parallel标记可以指定同时运行测试用例的最大并发执行数,但仅适用于单个二进制测试文件,通过修改GOMAXPROCS的值实现,该标记的默认值是测试运行时的最大 P 数量。

可以通过在测试函数中添加t.Parallel()调用,以同时运行多个功能测试。默认情况下 go test 在运行测试时,为了加快测试速度,package 是被并行运行的,但每个包中的功能测试用例是被串行执行的。

func TestParallelPrintHello(t *testing.T) {
	t.Parallel()
	for i := 0; i < 5; i++ {
		time.Sleep(200 * time.Millisecond)
		t.Log("==========Hello==========")
	}

}

func TestParallelPrintWorld(t *testing.T) {
	t.Parallel()
	t.Log("number of runtime.GOMAXPROCS:", runtime.GOMAXPROCS(0))
	for i := 0; i < 5; i++ {
		time.Sleep(200 * time.Millisecond)
		t.Log("==========World==========")
	}
}

使用t.Parallel()指定需要并行执行的测试,在测试结果中可以看到TestParallelPrintWorld与TestParallelPrintHello交替输出,说明这两个测试函数是并行执行的

go test -v -parallel=8 gott/hello
=== RUN   TestParallelPrintHello
=== PAUSE TestParallelPrintHello
=== RUN   TestParallelPrintWorld
=== PAUSE TestParallelPrintWorld
=== CONT  TestParallelPrintHello
=== CONT  TestParallelPrintWorld
    hello_test.go:43: number of runtime.GOMAXPROCS: 4
=== CONT  TestParallelPrintHello
    hello_test.go:36: ==========Hello==========
=== CONT  TestParallelPrintWorld
    hello_test.go:46: ==========World==========
    hello_test.go:46: ==========World==========
=== CONT  TestParallelPrintHello
    hello_test.go:36: ==========Hello==========
    hello_test.go:36: ==========Hello==========
=== CONT  TestParallelPrintWorld
    hello_test.go:46: ==========World==========
    hello_test.go:46: ==========World==========
=== CONT  TestParallelPrintHello
    hello_test.go:36: ==========Hello==========
=== CONT  TestParallelPrintWorld
    hello_test.go:46: ==========World==========
=== CONT  TestParallelPrintHello
    hello_test.go:36: ==========Hello==========
--- PASS: TestParallelPrintWorld (1.02s)
--- PASS: TestParallelPrintHello (1.02s)
PASS
ok      gott/hello      1.026s
  • 测试结果中runtime.GOMAXPROCS: 4 的值为4,并不是指定的8,说明-parallel标记并没有生成,因为测试运行的不是二进制文件,此时,即使不指定-parallel也是可以的,如果想让更多的测试用例同时运行,可以在运行go test时,指定-p参数,运行更多的P,来同时执行更多的测试用例,比如设置p的值为4,让同时并行执行的最大并发数为4
  • 使用t.Parallel()后,输出的信息多了PAUSECONT字段

如果加入-count=2标记,对于测试用例来说,此时的并发不会是2个count之间的并发,这两个count也是串行执行的

go test -v -count=2  gott/hello
# 这里是第一遍count
=== RUN   TestParallelPrintHello
=== PAUSE TestParallelPrintHello
=== RUN   TestParallelPrintWorld
=== PAUSE TestParallelPrintWorld
=== CONT  TestParallelPrintHello
=== CONT  TestParallelPrintWorld
    hello_test.go:43: number of runtime.GOMAXPROCS: 4
=== CONT  TestParallelPrintHello
    hello_test.go:36: ==========Hello==========
=== CONT  TestParallelPrintWorld
    hello_test.go:46: ==========World==========
=== CONT  TestParallelPrintHello
    hello_test.go:36: ==========Hello==========
=== CONT  TestParallelPrintWorld
    hello_test.go:46: ==========World==========
=== CONT  TestParallelPrintHello
    hello_test.go:36: ==========Hello==========
=== CONT  TestParallelPrintWorld
    hello_test.go:46: ==========World==========
=== CONT  TestParallelPrintHello
    hello_test.go:36: ==========Hello==========
=== CONT  TestParallelPrintWorld
    hello_test.go:46: ==========World==========
    hello_test.go:46: ==========World==========
--- PASS: TestParallelPrintWorld (1.02s)
=== CONT  TestParallelPrintHello
    hello_test.go:36: ==========Hello==========
--- PASS: TestParallelPrintHello (1.02s)

# 这里是第二遍count
=== RUN   TestParallelPrintHello
=== PAUSE TestParallelPrintHello
=== RUN   TestParallelPrintWorld
=== PAUSE TestParallelPrintWorld
=== CONT  TestParallelPrintHello
=== CONT  TestParallelPrintWorld
    hello_test.go:43: number of runtime.GOMAXPROCS: 4
    hello_test.go:46: ==========World==========
=== CONT  TestParallelPrintHello
    hello_test.go:36: ==========Hello==========
    hello_test.go:36: ==========Hello==========
=== CONT  TestParallelPrintWorld
    hello_test.go:46: ==========World==========
    hello_test.go:46: ==========World==========
=== CONT  TestParallelPrintHello
    hello_test.go:36: ==========Hello==========
    hello_test.go:36: ==========Hello==========
=== CONT  TestParallelPrintWorld
    hello_test.go:46: ==========World==========
    hello_test.go:46: ==========World==========
=== CONT  TestParallelPrintHello
    hello_test.go:36: ==========Hello==========
--- PASS: TestParallelPrintWorld (1.01s)
--- PASS: TestParallelPrintHello (1.01s)
PASS
ok      gott/hello      2.036s

源码文件

// hello.go
func main() {
	hello("max")
}

func hello(name string) string {
	return fmt.Sprint("hello ", name)
}

// hello_test.go
func TestHello(t *testing.T) {
	name := "Max"
	expected := fmt.Sprintf("hello %s", name)
	greeting := hello(name)
	if greeting != expected {
		t.Errorf("hello(%s) = %s, expected = %s", name, greeting, expected)
	}
	t.Log(expected)
	t.Log("number of runtime.GOMAXPROCS:", runtime.GOMAXPROCS(0))

}

func TestPrint(t *testing.T) {
	t.Log("just Print")
}

func BenchmarkHello(b *testing.B) {
	for i := 0; i < b.N; i++ {
		hello("Max")
	}
	b.Log("NNNNN:", b.N)
}

func TestParallelPrintHello(t *testing.T) {
	t.Parallel()
	for i := 0; i < 5; i++ {
		time.Sleep(200 * time.Millisecond)
		t.Log("==========Hello==========")
	}

}

func TestParallelPrintWorld(t *testing.T) {
	t.Parallel()
	t.Log("number of runtime.GOMAXPROCS:", runtime.GOMAXPROCS(0))
	for i := 0; i < 5; i++ {
		time.Sleep(200 * time.Millisecond)
		t.Log("==========World==========")
	}
}

小结

  • 可以通过运行runtime.GOMAXPROCS(0)获得运行时的最大P的数量
  • 在功能测试中,无法避免依赖一些外部环境,比如数据库的连接,第三方接口的调用,应该对这些外部环境进行仿造(mock),从而得到一个纯净的测试环境
  • go help testflag 查看 flag 帮助文件