• 单元测试llll


    原则

    1. 含有单元测试代码的 go 文件必须以 _test.go 结尾
    2. 单元测试的函数名必须以 Test 开头,测试函数的签名必须接收一个指向 testing.T 类型的指针,并且不能返回任何值。
    3. 单元测试文件名 _test.go 前面的部分最好是被测试的函数所在的 go 文件的文件名。

    定义一个斐波拉契函数

    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)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    编写相应的测试方法

    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)
    		}
    	}
    }
    
    • 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

    运行命令

    go test -v .//运行整组测试
    g
    
    • 1
    • 2
    ~/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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    断言

    go get -u github.com/stretchr/testify
    
    • 1
    • Every assert func takes the testing.T object as the first argument. This is how it writes the errors out through the normal go test capabilities.
    • Every assert func returns a bool indicating whether the assertion was successful or not, this is useful for if you want to go on making further assertions under certain conditions.
    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)
    	}
    }
    
    • 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
    ~/study/go/go/gin-example/routers/test💚💚💚💚go test -v .
    === RUN   TestFibonacci
    --- PASS: TestFibonacci (0.00s)
    PASS
    ok  	gin-example/routers/test	0.429s
    
    • 1
    • 2
    • 3
    • 4
    • 5

    常用assert

    • assert.Equal
    • assert.NotEqual
    • assert.Zero
    • assert.NotZero
    • assert.Nil
    • assert.NotNil

    将通用逻辑提取出来,使用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)
    	}
    }
    
    • 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
    • 39

    运行整组测试

    go test -v .
    
    • 1
    ~/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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    运行部分测试

    ~/study/go/go/gin-example/routers/test💚💚💚💚go test -run TestFibonacci
    test start
    PASS
    test end
    ok  	gin-example/routers/test	0.120s
    
    • 1
    • 2
    • 3
    • 4
    • 5

    测试覆盖率

    ~/study/go/go/gin-example/routers/test💚💚💚💚go test -cover .
    ok  	gin-example/routers/test	0.449s	coverage: 85.7% of statements
    
    • 1
    • 2
    go test -cover -coverprofile=c.out//保持测试结果
    go tool cover -html=c.out//打开测试文件
    
    • 1
    • 2

    测试结果

    mock

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

    使用命令将源文件的接口生成mock对象:

    mockgen -source=[源文件] -destination=[生成后的mock文件] -package=[包名]
    
    • 1

    基准测试

    测量一个程序在固定工作负载下的性能

    • 文件必须以*_test.go的文件为结尾
    • 方法必须以Benchmark为前缀
    • 函数必须接受一个指向Benchmark类型的指针作为唯一参数,*testing.B
    • 不能有返回值
    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) }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    ~/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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    结合pprof分析

    • -cpuprofile [file]输出cpu性能文件
    • -memprofile [file]输出mem内存性能文件
    go tool pprof [file]//查看报告
    
    • 1
    ~/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)
    
    • 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

    模糊测试

    https://go.dev/doc/tutorial/fuzz
    一种自动化的测试方法、通过连续不断地向程序提供输入,再通过fuzz来得到变异的输入,来尝试把函数跑崩,或是发现bug,可以暴露一些易被忽略的边界情形bug。因为无法预期输入,所以无法控制输出。Fuzzing 可以发现的漏洞包括 SQL 注入、缓冲区溢出、分布式拒绝服务攻击攻击和跨网站脚本攻击。一个xxx_test.go文件中,仅能有一个FuzzXXX测试函数。

    fuzz可变异的数据类型,仅为

    • ​​​​string​​,​​[]byte​​
    • ​​​​int​​,​​int8​​,​​int16​​,​​int32​​/​​rune​​,​​int64​​
    • ​​​​uint​​,​​uint8​​/​​byte​​,​​uint16​​,​​uint32​​,​​uint64​​
    • ​​​​float32​​,​​float64​​
    • ​​bool​​
    //待测试方法
    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)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    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)
    		}
    	})
    }
    
    • 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
    • 39
    • 40
    • 41

    首先应该进行单元测试,确保种子正确输入

    ~/study/go/go/gin-example/routers/test💚💚💚💚go test -run=TestReverse
    test start
    PASS
    test end
    ok  	gin-example/routers/test	0.106s
    
    • 1
    • 2
    • 3
    • 4
    • 5

    再进行模糊测试

    go test -fuzz=Fuzz
    
    • 1
    ~/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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    如上所示,在模糊测试时发生失败,导致问题的输入会被写入种子语料库文件,该文件将在下次调用 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    查看导致失败的输入,打开写入 testdata/fuzz/FuzzReverse 目录的语料库文件。
    在这里插入图片描述
    添加日志信息

    t.Logf()
    
    • 1
    ~/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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    整个种子语料库使用字符串,其中每个字符都是一个字节。但是,类似“泃”这样的字符可能需要几个字节。因此,逐字节逆转字符串将使多字节字符失效
    修复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💚💚💚💚
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    再次运行测试,发现了新的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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    修复之后运行:
    我们没有设置运行时间,会一直运行下去直到手动停止

    ~/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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    通过设置-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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
  • 相关阅读:
    SpringBoot面试题(100+)
    四大含金量高的算法证书考试
    Java 第一阶段建立编程思想 【枚举和注解】
    MES系统管理范围及标准
    性能超越 Clickhouse | 物联网场景中的毫秒级查询案例
    Android入门第32天-Android中的Alert Dialog的使用大全
    查看apk是否签名成功
    2022前端笔试题总结
    基于 SpringBoot+Vue的电影影城管理系统,附源码,数据库
    mybatis-plus-ext注解学习
  • 原文地址:https://blog.csdn.net/qq_45457760/article/details/125507892