Eli's Blog

1. 基础测试

1.1 最简单的测试

1
2
3
4
5
6
7
func Add(x, y int) int {
return x + y
}

func Sub(x, y int) int {
return x - y
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func TestAdd(t *testing.T) {
result := Add(3, 5)

if result != 8 {
t.Fatalf("expected: %d, actual: %d", 8, result)
}

t.Log("test Add success.")
}

func TestSub(t *testing.T) {
result := Sub(3, 5)

if result != -2 {
t.Fatalf("expected: %d, actual: %d", -2, result)
}

t.Log("test Sub success.")
}
1
2
3
4
5
6
7
go test

go test -v

go test -v -run TestAdd

go help testflag

1.2 表格驱动测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func TestAddBatch(t *testing.T) {
tests := []struct{
a, b, c int
}{
{1, 2, 3},
{0, 2, 2},
{-1, 1, 1},
}

for _, test := range tests {
if actual := Add(test.a, test.b); actual != test.c {
t.Errorf("Add(%d, %d); got %d; expected %d\n", test.a, test.b, actual, test.c)
}
}
}

1.3 覆盖率测试

1
2
3
go test -coverprofile=c.out

go tool cover -html=c.out

1.4 Example Code测试

1
2
3
4
5
6
7
func Fib(n int) int {
if n <= 2 {
return 1
}

return Fib(n-1) + Fib(n-2)
}
1
2
3
4
5
func ExampleFib() {
fmt.Println(fib(10))

// Output: 55
}
1
go test -v

2. 基准测试

基准测可以测试一段程序的运行性能及耗费CPU的程度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
func fib(n int) int {
a, b := 1, 1
for i := 1; i <= n; i++ {
if i == n {
break
}
a, b = b, a+b
}

return a
}

func fibonacci() func() int {
a, b := 1, 1
return func() int {
x := a
a, b = b, a+b
return x
}
}

func fib2(n int) int {
x := 0
f := fibonacci()
for i := 1; i <= n; i++ {
x = f()
}

return x
}

func fib3(n int) int {
if n <= 2 {
return 1
}

return fib3(n-1) + fib3(n-2)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
func BenchmarkFib(b *testing.B) {
n := 9
expected := 34

for i := 0; i < b.N; i++ {
actual := fib(n)
if actual != expected {
b.Errorf("fib(%d), got %d, expected %d", n, actual, expected)
}
}
}

func BenchmarkFib2(b *testing.B) {
n := 9
expected := 34

for i := 0; i < b.N; i++ {
actual := fib2(n)
if actual != expected {
b.Errorf("fib2(%d), got %d, expected %d", n, actual, expected)
}
}
}

func BenchmarkFib3(b *testing.B) {
n := 9
expected := 34

for i := 0; i < b.N; i++ {
actual := fib3(n)
if actual != expected {
b.Errorf("fib3(%d), got %d, expected %d", n, actual, expected)
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
go test -bench=.

BenchmarkFib-4 222371659 5.33 ns/op
BenchmarkFib2-4 13082476 85.9 ns/op
BenchmarkFib3-4 10054833 120 ns/op

# 自定义测试时间
go test -bench=. -benchmem -benchtime=10s

BenchmarkFib-4 1000000000 5.35 ns/op 0 B/op 0 allocs/op
BenchmarkFib2-4 138880591 87.1 ns/op 48 B/op 3 allocs/op
BenchmarkFib3-4 97723549 120 ns/op 0 B/op 0 allocs/op
  • ns/op 表示每一个操作消耗多少时间,单位是 纳秒ns
  • B/op 表示每一次操作需要分配的字节数
  • allocs/op 表示每次执行分配了多少次
1
2
3
4
go test -bench . -cpuprofile=cpu.out

go tool pprof cpu.out
(pprof) web

2.1 性能对比,int转string

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func BenchmarkSprintf(b *testing.B) {
num := 10
b.ResetTimer()
for i := 0; i < b.N; i++ {
fmt.Sprintf("%d", num)
}
}

func BenchmarkFormat(b *testing.B) {
num := int64(10)
b.ResetTimer()
for i := 0; i < b.N; i++ {
strconv.FormatInt(num, 10)
}
}

func BenchmarkItoa(b *testing.B) {
num := 10
b.ResetTimer()
for i := 0; i < b.N; i++ {
strconv.Itoa(num)

}
}
1
2
3
4
5
6
7
8
9
 go test -bench=. -benchmem
goos: darwin
goarch: amd64
pkg: gomod/aaa
BenchmarkSprintf-4 12190561 94.1 ns/op 16 B/op 2 allocs/op
BenchmarkFormat-4 275836423 4.24 ns/op 0 B/op 0 allocs/op
BenchmarkItoa-4 253071742 4.73 ns/op 0 B/op 0 allocs/op
PASS
ok gomod/aaa 5.386s

2.2 pprof 性能监控

1
2
3
4
5
6
7
8
9
10
11
12
13
func Fib(n int) int {
if n < 2 {
return n
}

return Fib(n-1) + Fib(n-2)
}

func BenchmarkFib(b *testing.B) {
for i := 0; i < b.N; i++ {
Fib(10)
}
}
1
2
3
4
5
6
7
go test -bench=. -benchmem -cpuprofile cpu.out -memprofile mem.out

go tool pprof cpu.out
(pprof) top
(pprof) list Fib

go tool pprof -http=":8081" cpu.out

3. gomock

当待测试的函数/对象的依赖关系很复杂,并且有些依赖不能直接创建,例如数据库连接、文件I/O等。这种场景就非常适合使用 mock/stub 测试。简单来说,就是用 mock 对象模拟依赖项的行为。

3.1 安装

1
2
go get -u github.com/golang/mock/gomock
go get -u github.com/golang/mock/mockgen

3.2 示例

3.2.1 待mock的代码

1
2
3
4
5
6
7
8
9
10
11
type DB interface {
Get(key string) (int, error)
}

func GetFromDB(db DB, key string) int {
if value, err := db.Get(key); err == nil {
return value
}

return -1
}

3.2.2 生成mock代码

1
mockgen -source=db.go -destination=db_mock.go -package=main

3.2.3 测试代码

1
2
3
4
5
6
7
8
9
10
11
func TestGetFromDB(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

m := NewMockDB(ctrl)
m.EXPECT().Get(gomock.Eq("Tom")).Return(0, errors.New("not exist"))

if v := GetFromDB(m, "Tom"); v != -1 {
t.Fatalf("expected -1, but go %v", v)
}
}

3.2.4 执行测试

1
2
3
4
5
6
$ go test . -cover -v
=== RUN TestGetFromDB
--- PASS: TestGetFromDB (0.00s)
PASS
coverage: 92.9% of statements
ok gomod/mock 1.030s coverage: 92.9% of statements

3.3 打桩 (stubs)

3.3.1 参数 (Eq, Any, Not, Nil)

1
2
3
4
m.EXPECT().Get(gomock.Eq("Tom")).Return(0, errors.New("not exist"))
m.EXPECT().Get(gomock.Any()).Return(630, nil)
m.EXPECT().Get(gomock.Not("Sam")).Return(0, nil)
m.EXPECT().Get(gomock.Nil()).Return(0, errors.New("nil"))

3.3.2 返回值 (Return, Do, DoAndReturn)

1
2
3
4
5
6
7
8
9
10
m.EXPECT().Get(gomock.Not("Sam")).Return(0, nil)
m.EXPECT().Get(gomock.Any()).Do(func(key string) {
t.Log(key)
})
m.EXPECT().Get(gomock.Any()).DoAndReturn(func(key string) (int, error) {
if key == "Sam" {
return 630, nil
}
return 0, errors.New("not exist")
})

3.3.3 调用次数 (Times, MaxTimes, MinTimes, AnyTimes)

1
2
3
4
5
6
7
8
9
func TestGetFromDB(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

m := NewMockDB(ctrl)
m.EXPECT().Get(gomock.Not("Sam")).Return(0, nil).Times(2)
GetFromDB(m, "ABC")
GetFromDB(m, "DEF")
}

调用顺序 (InOrder)

1
2
3
4
5
6
7
8
9
10
11
func TestGetFromDB(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish() // 断言 DB.Get() 方法是否被调用

m := NewMockDB(ctrl)
o1 := m.EXPECT().Get(gomock.Eq("Tom")).Return(0, errors.New("not exist"))
o2 := m.EXPECT().Get(gomock.Eq("Sam")).Return(630, nil)
gomock.InOrder(o1, o2)
GetFromDB(m, "Tom")
GetFromDB(m, "Sam")
}