前言:
平時(shí)根據(jù)需求寫代碼.人工進(jìn)行測試往往不會(huì)面面俱到,還會(huì)因?yàn)樾枨蟮母淖兎爆嵉倪M(jìn)行測試
通過完成一個(gè)測試函數(shù),可以大大簡化測試的步驟,并且在需求該變的時(shí)候只需要改變一下測試的輸入與期望
一、單元測試分類及其概念
1.基本分類
- 測試函數(shù) 函數(shù)前綴為Test 主要用于測試程序的一些邏輯行為是否正確
-
基準(zhǔn)函數(shù) 函數(shù)名前綴為
Benchmark
主要測試函數(shù)的性能 -
示例函數(shù) 函數(shù)的前綴名為
Example
為文檔提示示例文檔
2.細(xì)說單元測試分類
①測試函數(shù)
- 函數(shù)的基本測試
- 函數(shù)的組測試
- 函數(shù)的子測試
- 測試函數(shù)覆蓋率(也就是被測試函數(shù)有多少代碼用于了執(zhí)行)
-
在進(jìn)行測試的時(shí)候,要保證被測函數(shù)覆蓋率為100%,測試函數(shù)覆蓋率為60%以上,否則寫的代碼大部分用不到,需要優(yōu)化
-
①測試覆蓋率可以使用
go test -cover
-
②
go test -cover -coverprofile=c.out
(將測試的結(jié)果存入文件c.out) -
然后使用
go tool cover -html=c.out
可以打開文件,顯示哪些代碼沒有執(zhí)行
-
①測試覆蓋率可以使用
②基準(zhǔn)測試
- 基準(zhǔn)函數(shù)測試是做什么的:基準(zhǔn)函數(shù)會(huì)自定義一個(gè)時(shí)間段用于執(zhí)行代碼,如果代碼簡潔,被測函數(shù)的執(zhí)行次數(shù)需要成倍增加(直到達(dá)到基準(zhǔn)測試函數(shù)給的預(yù)期,然后統(tǒng)計(jì)一共執(zhí)行了多少輪,,每輪平均用時(shí)多少)
- 執(zhí)行基準(zhǔn)函數(shù)時(shí),首先要寫基準(zhǔn)函數(shù)
- 基準(zhǔn)測試函數(shù)的參數(shù)是:*testing.B對(duì)應(yīng)的指針變量
- 在測試函數(shù)內(nèi)部要進(jìn)行循環(huán),循環(huán)的終止條件是b.N
二、結(jié)合代碼細(xì)說每一種測試
1.基準(zhǔn)測試
(1)基準(zhǔn)測試函數(shù)需要注意的點(diǎn)
進(jìn)行基準(zhǔn)測試時(shí)往往是對(duì)函數(shù)的算法進(jìn)行測驗(yàn),有時(shí)后一個(gè)算法在測試數(shù)據(jù)的基量不同時(shí)測試出的效果會(huì)不同我們需要對(duì)不同數(shù)量級(jí)的樣本進(jìn)行測試的時(shí)候??梢宰约簩懸粋€(gè)盒子作為跳板,測試什么數(shù)量級(jí)的數(shù)據(jù)就只進(jìn)行當(dāng)前數(shù)量級(jí)的測試
測試的時(shí)候命令為:go test -bench=.(將所有的跳板函數(shù)均執(zhí)行一遍)或者=特定函數(shù)進(jìn)行特定函數(shù)測試-benchtime=time (在被測試函數(shù)在基準(zhǔn)函數(shù)默認(rèn)的時(shí)間內(nèi)執(zhí)行不完時(shí)可以拿這個(gè)參數(shù)進(jìn)行時(shí)間的擴(kuò)充)在進(jìn)行基準(zhǔn)函數(shù)測試的時(shí)候,可能需要進(jìn)行一些前置工作,如果感覺前置工作浪費(fèi)時(shí)間可以使用 b.ResetTimer()對(duì)計(jì)時(shí)器進(jìn)行重置
(2)基準(zhǔn)測試代碼
測試函數(shù)代碼如下:
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
|
// 文件名 subStr_test.go package main import ( "reflect" "testing" ) func BenchmarkSubStr(b *testing.B) { for i := 0; i < b.N; i++ { res := subStr( "qwe:qw:es:wqe" , ":" ) if !reflect.DeepEqual(res, []string{ "qwe" , "qw" , "es" , "wqe" }) { b.Errorf( "不匹配" ) } } } func benchmarkFib(b *testing.B, n int) { for i := 0; i < b.N; i++ { Fib(n) } } func BenchmarkFib5(b *testing.B) { benchmarkFib(b, 5) } func BenchmarkFib10(b *testing.B) { benchmarkFib(b, 10) } func BenchmarkFib15(b *testing.B) { benchmarkFib(b, 15) } func BenchmarkFib20(b *testing.B) { benchmarkFib(b, 20) } |
被測函數(shù)代碼如下:
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
42
|
// 文件名 subStr.go package main import ( "fmt" "strings" ) func subStr(str, stre string) []string { index := strings.Index(str, stre) var theSub []string for index >= 0 { // 先將分隔符前面的數(shù)據(jù)進(jìn)行保存 temp := str[:index] // 將字符串往后移 str = str[index+1:] // 重新獲取下標(biāo) index = strings.Index(str, stre) if temp != "" { theSub = append(theSub, temp) } else { continue } } theSub = append(theSub, str) return theSub[:] } // 菲薄那契數(shù)列 func Fib(n int) int { if n < 2 { return n } return Fib(n-1) + Fib(n-2) } func main() { fmt .Println(subStr( "q:w:ec:wer:cd:scn:cj:c:is:icc:cin:si" , ":" )) fmt .Printf( "%#v\n" , subStr( "q:w:ec:wer:cd:scn:cj:c:is:icc:cin:si" , ":" )) fmt .Println(123) } |
將以上兩個(gè)文件放在同一目錄下,執(zhí)行測試命令 go test
得到測試結(jié)果:
goos: windows
goarch: amd64
cpu: Intel(R) Core(TM) i5-8265U CPU @ 1.60GHz
BenchmarkSubStr-8 1213681 1012 ns/op 352 B/op 14 allocs/op
PASS
ok _/d_/Go語言學(xué)習(xí)筆記/go語言語法部分/go語言進(jìn)階語法/8.單元測試/基準(zhǔn)測試 2.410s
2.組測試與子測試
- 對(duì)于一個(gè)函數(shù)的測試,不僅僅是一組測試用例,所以我們需要寫測試組, 對(duì)一個(gè)函數(shù)進(jìn)行測試,測試的時(shí)候一般用集合鍵值對(duì)構(gòu)成,每一個(gè)鍵對(duì)應(yīng)一組測試數(shù)據(jù)
- 進(jìn)行組測試的時(shí)候可能會(huì)有個(gè)別的組出錯(cuò),所以使用子測試可以對(duì)某個(gè)案例進(jìn)行單獨(dú)測試這就是測試組與子測試的產(chǎn)生背景
測試函數(shù)如下:
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
42
43
44
45
46
47
48
49
50
51
|
package main import ( "reflect" "testing" ) type testS struct { str string ste string want []string } // 進(jìn)行組測試,批量進(jìn)行測試,如果有哪個(gè)地方不滿足則進(jìn)行異常拋出 // func TestSubStr(t *testing.T) { // testMap := map[string]testS{ // "case_1" : { "123:eqwe:123" , ":" , []string{ "123" , "eqwe" , "123" }}, // "case_2" : { "13:eqwe3:1@23" , "3:" , []string{ "1" , "eqwe" , "1@23" }}, // "case_3" : { "12@3@:@eq@we@:1@23" , "@:" , []string{ "12@3" , "@eq@we" , "1@23" }}, // "case_4" : { "123:eq@we:1@2@3" , "q@" , []string{ "123:e" , "we:1@2@3" }}, // "case_5" : { "123:eqwe:123" , "2" , []string{ "1" , "3:eqwe:1" , "3" }}, // "case_6" : { "123:eqwe:123" , "eqwe" , []string{ "123:" , ":123" }}, // } // for k, v := range testMap { // res := subStr( v .str, v .ste) // if !reflect.DeepEqual(res, v .want) { // t.Errorf( "%v want:%#v got:%#v" , k, v .want, res) // } // } // } // 進(jìn)行子測試,可以針對(duì)某個(gè)子樣本進(jìn)行測試 func TestSubStr(t *testing.T) { testMap := map[string]testS{ "case_1" : { "13:eqwe:123" , ":" , []string{ "123" , "eqwe" , "123" }}, "case_2" : { "3:eqwe3:1@23" , "3:" , []string{ "1" , "eqwe" , "1@23" }}, "case_3" : { "2@3@:@eq@we@:1@23" , "@:" , []string{ "12@3" , "@eq@we" , "1@23" }}, "case_4" : { "123:eq@we:1@2@3" , "q@" , []string{ "123:e" , "we:1@2@3" }}, "case_5" : { "23:eqwe:123" , "2" , []string{ "1" , "3:eqwe:1" , "3" }}, "case_6" : { "123:eqwe:123" , "eqwe" , []string{ "123:" , ":123" }}, } for k, v := range testMap { t.Run(k, func(t *testing.T) { res := subStr( v .str, v .ste) if !reflect.DeepEqual(res, v .want) { t.Errorf( "want:%#v got:%#v" , v .want, res) } }) } } |
待測函數(shù)如下:
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
|
package main import ( "fmt" "strings" ) func subStr(str, stre string) []string { index := strings.Index(str, stre) var theSub []string for index >= 0 { // 先將分隔符前面的數(shù)據(jù)進(jìn)行保存 temp := str[:index] // 將字符串往后移 str = str[index+len(stre):] // 重新獲取下標(biāo) index = strings.Index(str, stre) if temp != "" { theSub = append(theSub, temp) } else { continue } } theSub = append(theSub, str) return theSub[:] } func main() { fmt .Println(subStr( "q:w:ec:wer:cd:scn:cj:c:is:icc:cin:si" , ":c" )) fmt .Printf( "%#v\n" , subStr( "q:w:ec:wer:cd:scn:cj:c:is:icc:cin:si" , ":" )) fmt .Println(123) } |
(1).組測試結(jié)果分析及命令:
-
組測試命令仍使用
go test
--- FAIL: TestSubStr (0.00s)
--- FAIL: TestSubStr/case_1 (0.00s)
subStr_test.go:46: want:[]string{"123", "eqwe", "123"} got:[]string{"13", "eqwe", "123"}
--- FAIL: TestSubStr/case_2 (0.00s)
subStr_test.go:46: want:[]string{"1", "eqwe", "1@23"} got:[]string{"eqwe", "1@23"}
--- FAIL: TestSubStr/case_3 (0.00s)
subStr_test.go:46: want:[]string{"12@3", "@eq@we", "1@23"} got:[]string{"2@3", "@eq@we", "1@23"}
--- FAIL: TestSubStr/case_5 (0.00s)
subStr_test.go:46: want:[]string{"1", "3:eqwe:1", "3"} got:[]string{"3:eqwe:1", "3"}
FAIL
exit status 1
FAIL _/D_/Go語言學(xué)習(xí)筆記/go語言語法部分/go語言進(jìn)階語法/8.單元測試/組測試與子測試 0.155s
(2).子測試結(jié)果分析及命令:
-
例如單獨(dú)測試case_1,使用的命令為
go test -v -run=TestSubStr/case_1
(等號(hào)后面跟的是上面組測試失敗案例FAIL后面的目錄)
=== RUN TestSubStr
=== RUN TestSubStr/case_1
subStr_test.go:46: want:[]string{"123", "eqwe", "123"} got:[]string{"13", "eqwe", "123"}
--- FAIL: TestSubStr (0.00s)
--- FAIL: TestSubStr/case_1 (0.00s)
FAIL
exit status 1
FAIL _/D_/Go語言學(xué)習(xí)筆記/go語言語法部分/go語言進(jìn)階語法/8.單元測試/組測試與子測試 0.186s
三、pprof調(diào)試工具
1.對(duì)主函數(shù)進(jìn)行傳參
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
(1)os.Args package main1 import ( "fmt" "os" ) func main() { // os.Args可以在執(zhí)行函數(shù)的時(shí)候傳遞參數(shù),但是對(duì)于-name=xxx沒有辦法將其解析 if os.Args != nil { for index, temp := range os.Args { fmt .Println( "第" , index+1, "個(gè)參數(shù)是:" , temp) } } fmt .Println( "hello" ) } |
(2)flag.Args
- 相對(duì)于os.Args來說,flag.Args使用起來更加方便
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
|
package main import ( "flag" "fmt" "time" ) func main() { // 第一個(gè)參數(shù)為對(duì)應(yīng)的參數(shù),第二個(gè)參數(shù)為默認(rèn)值,第三個(gè)參數(shù)為提示 // 返回的是對(duì)應(yīng)類型的指針 // name := flag.String( "name" , "Tom" , "輸入name" ) // sex := flag.Bool( "sex" , true , "是不是男性" ) // age := flag.Int( "age" , 10, "年齡" ) // flag.Parse() // fmt .Println(*name, *sex, *age) var name string var age int var sex bool flag.StringVar(&name, "name" , "Tom" , "輸入name" ) flag.BoolVar(&sex, "sex" , true , "是不是男性" ) flag.IntVar(&age, "age" , 10, "年齡" ) tim := flag.Duration( "time" , time .Hour, "時(shí)間" ) // 將輸入的數(shù)據(jù)進(jìn)行解析,不使用這句話的話不能獲取到name sex age等屬性對(duì)應(yīng)的值 flag.Parse() fmt .Println(name, sex, age, *tim) fmt .Println(flag.Args()) // 以切片的方式返回命令行之外的其他參數(shù) fmt .Println(flag.NArg()) // 返回命令行之外的其他參數(shù)的個(gè)數(shù) fmt .Println(flag.NFlag()) // 返回使用命令行的參數(shù)的個(gè)數(shù) } |
2.pprof性能調(diào)優(yōu)
pprof
調(diào)試工具進(jìn)行調(diào)試,主要看待測模塊在內(nèi)存于時(shí)間上的效益調(diào)試的時(shí)候只會(huì)顯示耗費(fèi)時(shí)間空間較多的代碼段.
生成調(diào)試代碼塊的文件:go run xx.exe -cpu....
使用go語言工具查看代碼塊所存在的問題:go tool pprof cpu.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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
package main import ( "flag" "fmt" "os" "runtime/pprof" "time" ) // 一段有問題的代碼 func logicCode() { var c chan int for { select { case v := <-c: fmt .Printf( "recv from chan, value:%v\n" , v ) default: } } } func main() { var isCPUPprof bool var isMemPprof bool // 獲取接收到的參數(shù) flag.BoolVar(&isCPUPprof, "cpu" , false , "turn cpu pprof on" ) flag.BoolVar(&isMemPprof, "mem" , false , "turn mem pprof on" ) flag.Parse() // 判斷進(jìn)行什么測試,然后執(zhí)行分支,并將信息保存到相應(yīng)的文件內(nèi)。 if isCPUPprof { file , err := os.Create( "./cpu.pprof" ) if err != nil { fmt .Printf( "create cpu pprof failed, err:%v\n" , err) return } pprof.StartCPUProfile( file ) defer pprof.StopCPUProfile() } for i := 0; i < 8; i++ { go logicCode() } time .Sleep(20 * time .Second) if isMemPprof { file , err := os.Create( "./mem.pprof" ) if err != nil { fmt .Printf( "create mem pprof failed, err:%v\n" , err) return } pprof.WriteHeapProfile( file ) file .Close() } } |
總結(jié):
這里對(duì)常用的測試函數(shù),子測試進(jìn)行了詳細(xì)的講解,對(duì)于pprof參數(shù)調(diào)優(yōu)僅僅做了個(gè)介紹,可以根據(jù)自己的興趣在網(wǎng)上找一部分pprof的例子深入學(xué)習(xí),進(jìn)行自動(dòng)化測試非常方便。
到此這篇關(guān)于Go語言單元測試超詳細(xì)解析的文章就介紹到這了,更多相關(guān)Go語言單元測試內(nèi)容請(qǐng)搜索服務(wù)器之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持服務(wù)器之家!
原文鏈接:https://blog.csdn.net/apple_51931783/article/details/122563019