定义一个斐波拉契函数
package test
func Fibonacci(n int) int {
if n < 0 {
return 0
}
if n == 0 {
return 0
}
if n == 1 {
return 1
}
return Fibonacci(n-1) + Fibonacci(n-2)
}
编写相应的测试方法
package test
import (
"testing"
)
func TestFibonacci(t *testing.T) {
// 预先定义的一组斐波那契数列作为测试用例(表驱动)
caseMap := map[int]int{
0: 0,
1: 1,
2: 1,
3: 2,
4: 3,
5: 5,
6: 8,
7: 13,
8: 21,
}
for k, v := range caseMap {
fib := Fibonacci(k)
if v == fib {
t.Logf("结果正确:n为%d,值为%d", k, fib)
} else {
t.Errorf("结果错误:期望%d,但是计算的值是%d", v, fib)
}
}
}
运行命令
go test -v .//运行整组测试
g
~/study/go/go/gin-example/routers/test💚💚💚💚go test -v .
=== RUN TestFibonacci
fibonacci_test.go:24: 结果正确:n为5,值为5
fibonacci_test.go:24: 结果正确:n为7,值为13
fibonacci_test.go:24: 结果正确:n为0,值为0
fibonacci_test.go:24: 结果正确:n为1,值为1
fibonacci_test.go:24: 结果正确:n为2,值为1
fibonacci_test.go:24: 结果正确:n为3,值为2
fibonacci_test.go:24: 结果正确:n为4,值为3
fibonacci_test.go:24: 结果正确:n为6,值为8
fibonacci_test.go:24: 结果正确:n为8,值为21
--- PASS: TestFibonacci (0.00s)
PASS
ok gin-example/routers/test 0.503s
go get -u github.com/stretchr/testify
package test
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestFibonacci(t *testing.T) {
// 预先定义的一组斐波那契数列作为测试用例(表驱动)
caseMap := map[int]int{
0: 0,
1: 1,
2: 1,
3: 2,
4: 3,
5: 5,
6: 8,
7: 13,
8: 21,
}
for k, v := range caseMap {
fib := Fibonacci(k)
assert.Equal(t, fib, v)
}
}
~/study/go/go/gin-example/routers/test💚💚💚💚go test -v .
=== RUN TestFibonacci
--- PASS: TestFibonacci (0.00s)
PASS
ok gin-example/routers/test 0.429s
常用assert
将通用逻辑提取出来,使用setup()和teardown()方法
package test
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func setup() {
fmt.Println("test start")
}
func teardown() {
fmt.Println("test end")
}
func TestMain(m *testing.M) {
setup()
m.Run()
teardown()
}
func TestFibonacci(t *testing.T) {
// 预先定义的一组斐波那契数列作为测试用例(表驱动)
caseMap := map[int]int{
0: 0,
1: 1,
2: 1,
3: 2,
4: 3,
5: 5,
6: 8,
7: 13,
8: 21,
}
for k, v := range caseMap {
fib := Fibonacci(k)
assert.Equal(t, fib, v)
}
}
运行整组测试
go test -v .
~/study/go/go/gin-example/routers/test💚💚💚💚go test -v .
test start
=== RUN TestFibonacci
--- PASS: TestFibonacci (0.00s)
PASS
test end
ok gin-example/routers/test 3.055s
运行部分测试
~/study/go/go/gin-example/routers/test💚💚💚💚go test -run TestFibonacci
test start
PASS
test end
ok gin-example/routers/test 0.120s
~/study/go/go/gin-example/routers/test💚💚💚💚go test -cover .
ok gin-example/routers/test 0.449s coverage: 85.7% of statements
go test -cover -coverprofile=c.out//保持测试结果
go tool cover -html=c.out//打开测试文件
go get -u github.com/golang/mock/gomock
go get -u github.com/golang/mock/mockgen
使用命令将源文件的接口生成mock对象:
mockgen -source=[源文件] -destination=[生成后的mock文件] -package=[包名]
测量一个程序在固定工作负载下的性能
func benchmarkFib(n int, b *testing.B) {
for i := 0; i < b.N; i++ {
Fibonacci(n)
}
}
func BenchmarkFib1(b *testing.B) { benchmarkFib(1, b) }
func BenchmarkFib2(b *testing.B) { benchmarkFib(2, b) }
func BenchmarkFib3(b *testing.B) { benchmarkFib(3, b) }
func BenchmarkFib10(b *testing.B) { benchmarkFib(10, b) }
func BenchmarkFib20(b *testing.B) { benchmarkFib(20, b) }
func BenchmarkFib40(b *testing.B) { benchmarkFib(40, b) }
~/study/go/go/gin-example/routers/test💚💚💚💚go test -bench=.
test start
goos: darwin
goarch: arm64
pkg: gin-example/routers/test
BenchmarkFib1-8 517390146 2.029 ns/op
BenchmarkFib2-8 255578442 4.692 ns/op
BenchmarkFib3-8 174335670 6.912 ns/op
BenchmarkFib10-8 5110288 235.6 ns/op
BenchmarkFib20-8 40770 29509 ns/op
BenchmarkFib40-8 3 453750430 ns/op
PASS
test end
ok gin-example/routers/test 11.015s
go tool pprof [file]//查看报告
~/study/go/go/gin-example/routers/test💚💚💚💚go tool pprof cpu.out
Type: cpu
Time: Jun 29, 2022 at 1:49pm (CST)
Duration: 10.69s, Total samples = 8.19s (76.64%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof)
(pprof) list BenchmarkRib1
no matches found for regexp: BenchmarkRib1
(pprof) list BenchmarkFib1
Total: 8.19s
ROUTINE ======================== gin-example/routers/test.BenchmarkFib1 in /Users/bjsl/study/go/go/gin-example/routers/test/fibonacci_test.go
0 1.05s (flat, cum) 12.82% of Total
. . 40:func benchmarkFib(n int, b *testing.B) {
. . 41: for i := 0; i < b.N; i++ {
. . 42: Fibonacci(n)
. . 43: }
. . 44:}
. 1.05s 45:func BenchmarkFib1(b *testing.B) { benchmarkFib(1, b) }
. . 46:func BenchmarkFib2(b *testing.B) { benchmarkFib(2, b) }
. . 47:func BenchmarkFib3(b *testing.B) { benchmarkFib(3, b) }
. . 48:func BenchmarkFib10(b *testing.B) { benchmarkFib(10, b) }
. . 49:func BenchmarkFib20(b *testing.B) { benchmarkFib(20, b) }
. . 50:func BenchmarkFib40(b *testing.B) { benchmarkFib(40, b) }
ROUTINE ======================== gin-example/routers/test.BenchmarkFib10 in /Users/bjsl/study/go/go/gin-example/routers/test/fibonacci_test.go
0 1.18s (flat, cum) 14.41% of Total
. . 43: }
. . 44:}
. . 45:func BenchmarkFib1(b *testing.B) { benchmarkFib(1, b) }
. . 46:func BenchmarkFib2(b *testing.B) { benchmarkFib(2, b) }
. . 47:func BenchmarkFib3(b *testing.B) { benchmarkFib(3, b) }
. 1.18s 48:func BenchmarkFib10(b *testing.B) { benchmarkFib(10, b) }
. . 49:func BenchmarkFib20(b *testing.B) { benchmarkFib(20, b) }
. . 50:func BenchmarkFib40(b *testing.B) { benchmarkFib(40, b) }
(pprof)
https://go.dev/doc/tutorial/fuzz
一种自动化的测试方法、通过连续不断地向程序提供输入,再通过fuzz来得到变异的输入,来尝试把函数跑崩,或是发现bug,可以暴露一些易被忽略的边界情形bug。因为无法预期输入,所以无法控制输出。Fuzzing 可以发现的漏洞包括 SQL 注入、缓冲区溢出、分布式拒绝服务攻击攻击和跨网站脚本攻击。一个xxx_test.go文件中,仅能有一个FuzzXXX测试函数。
fuzz可变异的数据类型,仅为
//待测试方法
func Reverse(s string) string {
b := []byte(s)
for i, j := 0, len(b)-1; i < len(b)/2; i, j = i+1, j-1 {
b[i], b[j] = b[j], b[i]
}
return string(b)
}
package test
import (
"testing"
"unicode/utf8"
)
//一般的单元测试方法
func TestReverse(t *testing.T) {
testcases := []struct {
in, want string
}{
{"Hello, world", "dlrow ,olleH"},
{" ", " "},
{"!12345", "54321!"},
}
for _, tc := range testcases {
rev := Reverse(tc.in)
if rev != tc.want {
t.Errorf("Reverse: %q, want %q", rev, tc.want)
}
}
}
//模糊测试方法
func FuzzReverse(f *testing.F) {
testcase := []string{"Hello, world", " ", "!12345"}
for _, tc := range testcase {
f.Add(tc)
}
f.Fuzz(func(t *testing.T, orig string) {
rev := Reverse(orig)
doubleRev := Reverse(rev)
//将字符串反转两次看是否和原始值一致
if orig != doubleRev {
t.Errorf("Before: %q, after: %q", orig, doubleRev)
}
//反转的字符串是否任然为有效的 UTF-8格式
if utf8.ValidString(orig) && !utf8.ValidString(rev) {
t.Errorf("Reverse produced invalid UTF-8 string %q", rev)
}
})
}
首先应该进行单元测试,确保种子正确输入
~/study/go/go/gin-example/routers/test💚💚💚💚go test -run=TestReverse
test start
PASS
test end
ok gin-example/routers/test 0.106s
再进行模糊测试
go test -fuzz=Fuzz
~/study/go/go/gin-example/routers/test💚💚💚💚go test -fuzz=Fuzz
test start
fuzz: elapsed: 0s, gathering baseline coverage: 0/10 completed
failure while testing seed corpus entry: FuzzReverse/7fcf9968018c3cc4359d20ae4f526488bcd7ceb0b86fcf671e0267de4538c103
fuzz: elapsed: 0s, gathering baseline coverage: 0/10 completed
--- FAIL: FuzzReverse (0.03s)
--- FAIL: FuzzReverse (0.00s)
reverse_test.go:35: Reverse produced invalid UTF-8 string "\xb5\xd6"
FAIL
test end
exit status 1
FAIL gin-example/routers/test 0.469s
如上所示,在模糊测试时发生失败,导致问题的输入会被写入种子语料库文件,该文件将在下次调用 go test 时运行,即使没有 -fuzz 标志。
~/study/go/go/gin-example/routers/test💚💚💚💚go test
test start
--- FAIL: FuzzReverse (0.00s)
--- FAIL: FuzzReverse/7fcf9968018c3cc4359d20ae4f526488bcd7ceb0b86fcf671e0267de4538c103 (0.00s)
reverse_test.go:35: Reverse produced invalid UTF-8 string "\xb5\xd6"
FAIL
test end
exit status 1
FAIL gin-example/routers/test 0.145s
查看导致失败的输入,打开写入 testdata/fuzz/FuzzReverse 目录的语料库文件。
添加日志信息
t.Logf()
~/study/go/go/gin-example/routers/test💚💚💚💚go test -fuzz=Fuzz
test start
fuzz: elapsed: 0s, gathering baseline coverage: 0/10 completed
failure while testing seed corpus entry: FuzzReverse/7fcf9968018c3cc4359d20ae4f526488bcd7ceb0b86fcf671e0267de4538c103
fuzz: elapsed: 0s, gathering baseline coverage: 0/10 completed
--- FAIL: FuzzReverse (0.02s)
--- FAIL: FuzzReverse (0.00s)
reverse_test.go:35: Reverse produced invalid UTF-8 string "\xb5\xd6"
FAIL
test end
exit status 1
FAIL gin-example/routers/test 0.196s
整个种子语料库使用字符串,其中每个字符都是一个字节。但是,类似“泃”这样的字符可能需要几个字节。因此,逐字节逆转字符串将使多字节字符失效
修复bug后,运行一次之前失败的测试用例来检验是否已经修复:
~/study/go/go/gin-example/routers/test💚💚💚💚go test -run=FuzzReverse/7fcf9968018c3cc4359d20ae4f526488bcd7ceb0b86fcf671e0267de4538c103
test start
PASS
test end
ok gin-example/routers/test 0.448s
~/study/go/go/gin-example/routers/test💚💚💚💚
再次运行测试,发现了新的bug
~/study/go/go/gin-example/routers/test💚💚💚💚go test -fuzz=Fuzz
test start
fuzz: elapsed: 0s, gathering baseline coverage: 0/10 completed
fuzz: minimizing 38-byte failing input file
fuzz: elapsed: 0s, gathering baseline coverage: 5/10 completed
--- FAIL: FuzzReverse (0.02s)
--- FAIL: FuzzReverse (0.00s)
reverse_test.go:31: Number of runes: orig=1, rev=1, doubleRev=1
reverse_test.go:33: Before: "\xfa", after: "�"
Failing input written to testdata/fuzz/FuzzReverse/3940e35d8f93209765a5967d6a8b4d05c98517ba48195142cee653da85fa5e90
To re-run:
go test -run=FuzzReverse/3940e35d8f93209765a5967d6a8b4d05c98517ba48195142cee653da85fa5e90
FAIL
test end
exit status 1
FAIL gin-example/routers/test 0.335s
修复之后运行:
我们没有设置运行时间,会一直运行下去直到手动停止
~/study/go/go/gin-example/routers/test💚💚💚💚go test -fuzz=Fuzz
test start
input: "Hello, world"
runes: ['H' 'e' 'l' 'l' 'o' ',' ' ' 'w' 'o' 'r' 'l' 'd']
input: " "
runes: [' ']
input: "!12345"
runes: ['!' '1' '2' '3' '4' '5']
fuzz: elapsed: 0s, gathering baseline coverage: 0/11 completed
fuzz: elapsed: 0s, gathering baseline coverage: 11/11 completed, now fuzzing with 8 workers
fuzz: elapsed: 3s, execs: 199734 (66517/sec), new interesting: 74 (total: 85)
fuzz: elapsed: 6s, execs: 274664 (24984/sec), new interesting: 81 (total: 92)
fuzz: elapsed: 9s, execs: 657750 (127716/sec), new interesting: 97 (total: 108)
fuzz: elapsed: 12s, execs: 992014 (111385/sec), new interesting: 108 (total: 119)
fuzz: elapsed: 15s, execs: 1351472 (119880/sec), new interesting: 113 (total: 124)
fuzz: elapsed: 18s, execs: 1733945 (127504/sec), new interesting: 116 (total: 127)
^Cfuzz: elapsed: 20s, execs: 2045996 (131214/sec), new interesting: 119 (total: 130)
PASS
test end
ok gin-example/routers/test 20.778s
通过设置-fuzztime时间来停止测试
~/study/go/go/gin-example/routers/test💚💚💚💚go test -fuzz=Fuzz -fuzztime 5s
test start
input: "Hello, world"
runes: ['H' 'e' 'l' 'l' 'o' ',' ' ' 'w' 'o' 'r' 'l' 'd']
input: " "
runes: [' ']
input: "!12345"
runes: ['!' '1' '2' '3' '4' '5']
fuzz: elapsed: 0s, gathering baseline coverage: 0/130 completed
fuzz: elapsed: 0s, gathering baseline coverage: 130/130 completed, now fuzzing with 8 workers
fuzz: elapsed: 3s, execs: 395613 (131858/sec), new interesting: 6 (total: 136)
fuzz: elapsed: 5s, execs: 681026 (136103/sec), new interesting: 8 (total: 138)
PASS
test end
ok gin-example/routers/test 5.242s