Go語(yǔ)言單元測(cè)試超詳細(xì)解析
前言:
平時(shí)根據(jù)需求寫(xiě)代碼.人工進(jìn)行測(cè)試往往不會(huì)面面俱到,還會(huì)因?yàn)樾枨蟮母淖兎爆嵉倪M(jìn)行測(cè)試
通過(guò)完成一個(gè)測(cè)試函數(shù),可以大大簡(jiǎn)化測(cè)試的步驟,并且在需求該變的時(shí)候只需要改變一下測(cè)試的輸入與期望
一、單元測(cè)試分類及其概念
1.基本分類
- 測(cè)試函數(shù) 函數(shù)前綴為Test 主要用于測(cè)試程序的一些邏輯行為是否正確
- 基準(zhǔn)函數(shù) 函數(shù)名前綴為
Benchmark
主要測(cè)試函數(shù)的性能 - 示例函數(shù) 函數(shù)的前綴名為
Example
為文檔提示示例文檔
2.細(xì)說(shuō)單元測(cè)試分類
①測(cè)試函數(shù)
- 函數(shù)的基本測(cè)試
- 函數(shù)的組測(cè)試
- 函數(shù)的子測(cè)試
- 測(cè)試函數(shù)覆蓋率(也就是被測(cè)試函數(shù)有多少代碼用于了執(zhí)行)
- 在進(jìn)行測(cè)試的時(shí)候,要保證被測(cè)函數(shù)覆蓋率為100%,測(cè)試函數(shù)覆蓋率為60%以上,否則寫(xiě)的代碼大部分用不到,需要優(yōu)化
- ①測(cè)試覆蓋率可以使用
go test -cover
- ②
go test -cover -coverprofile=c.out
(將測(cè)試的結(jié)果存入文件c.out) - 然后使用
go tool cover -html=c.out
可以打開(kāi)文件,顯示哪些代碼沒(méi)有執(zhí)行
- ①測(cè)試覆蓋率可以使用
②基準(zhǔn)測(cè)試
- 基準(zhǔn)函數(shù)測(cè)試是做什么的:基準(zhǔn)函數(shù)會(huì)自定義一個(gè)時(shí)間段用于執(zhí)行代碼,如果代碼簡(jiǎn)潔,被測(cè)函數(shù)的執(zhí)行次數(shù)需要成倍增加(直到達(dá)到基準(zhǔn)測(cè)試函數(shù)給的預(yù)期,然后統(tǒng)計(jì)一共執(zhí)行了多少輪,,每輪平均用時(shí)多少)
- 執(zhí)行基準(zhǔn)函數(shù)時(shí),首先要寫(xiě)基準(zhǔn)函數(shù)
- 基準(zhǔn)測(cè)試函數(shù)的參數(shù)是:*testing.B對(duì)應(yīng)的指針變量
- 在測(cè)試函數(shù)內(nèi)部要進(jìn)行循環(huán),循環(huán)的終止條件是b.N
二、結(jié)合代碼細(xì)說(shuō)每一種測(cè)試
1.基準(zhǔn)測(cè)試
(1)基準(zhǔn)測(cè)試函數(shù)需要注意的點(diǎn)
進(jìn)行基準(zhǔn)測(cè)試時(shí)往往是對(duì)函數(shù)的算法進(jìn)行測(cè)驗(yàn),有時(shí)后一個(gè)算法在測(cè)試數(shù)據(jù)的基量不同時(shí)測(cè)試出的效果會(huì)不同我們需要對(duì)不同數(shù)量級(jí)的樣本進(jìn)行測(cè)試的時(shí)候??梢宰约簩?xiě)一個(gè)盒子作為跳板,測(cè)試什么數(shù)量級(jí)的數(shù)據(jù)就只進(jìn)行當(dāng)前數(shù)量級(jí)的測(cè)試
測(cè)試的時(shí)候命令為:go test -bench=.(將所有的跳板函數(shù)均執(zhí)行一遍)或者=特定函數(shù)進(jìn)行特定函數(shù)測(cè)試-benchtime=time (在被測(cè)試函數(shù)在基準(zhǔn)函數(shù)默認(rèn)的時(shí)間內(nèi)執(zhí)行不完時(shí)可以拿這個(gè)參數(shù)進(jìn)行時(shí)間的擴(kuò)充)在進(jìn)行基準(zhǔn)函數(shù)測(cè)試的時(shí)候,可能需要進(jìn)行一些前置工作,如果感覺(jué)前置工作浪費(fèi)時(shí)間可以使用 b.ResetTimer()對(duì)計(jì)時(shí)器進(jìn)行重置
(2)基準(zhǔn)測(cè)試代碼
測(cè)試函數(shù)代碼如下:
//文件名 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) }
被測(cè)函數(shù)代碼如下:
//文件名 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í)行測(cè)試命令 go test
得到測(cè)試結(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語(yǔ)言學(xué)習(xí)筆記/go語(yǔ)言語(yǔ)法部分/go語(yǔ)言進(jìn)階語(yǔ)法/8.單元測(cè)試/基準(zhǔn)測(cè)試 2.410s
2.組測(cè)試與子測(cè)試
- 對(duì)于一個(gè)函數(shù)的測(cè)試,不僅僅是一組測(cè)試用例,所以我們需要寫(xiě)測(cè)試組, 對(duì)一個(gè)函數(shù)進(jìn)行測(cè)試,測(cè)試的時(shí)候一般用集合鍵值對(duì)構(gòu)成,每一個(gè)鍵對(duì)應(yīng)一組測(cè)試數(shù)據(jù)
- 進(jìn)行組測(cè)試的時(shí)候可能會(huì)有個(gè)別的組出錯(cuò),所以使用子測(cè)試可以對(duì)某個(gè)案例進(jìn)行單獨(dú)測(cè)試這就是測(cè)試組與子測(cè)試的產(chǎn)生背景
測(cè)試函數(shù)如下:
package main import ( ?? ?"reflect" ?? ?"testing" ) type testS struct { ?? ?str ?string ?? ?ste ?string ?? ?want []string } //進(jìn)行組測(cè)試,批量進(jìn)行測(cè)試,如果有哪個(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)行子測(cè)試,可以針對(duì)某個(gè)子樣本進(jìn)行測(cè)試 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) ?? ??? ??? ?} ?? ??? ?}) ?? ?} }
待測(cè)函數(shù)如下:
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).組測(cè)試結(jié)果分析及命令:
- 組測(cè)試命令仍使用
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語(yǔ)言學(xué)習(xí)筆記/go語(yǔ)言語(yǔ)法部分/go語(yǔ)言進(jìn)階語(yǔ)法/8.單元測(cè)試/組測(cè)試與子測(cè)試 0.155s
(2).子測(cè)試結(jié)果分析及命令:
- 例如單獨(dú)測(cè)試case_1,使用的命令為
go test -v -run=TestSubStr/case_1
(等號(hào)后面跟的是上面組測(cè)試失敗案例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語(yǔ)言學(xué)習(xí)筆記/go語(yǔ)言語(yǔ)法部分/go語(yǔ)言進(jìn)階語(yǔ)法/8.單元測(cè)試/組測(cè)試與子測(cè)試 0.186s
三、pprof調(diào)試工具
1.對(duì)主函數(shù)進(jìn)行傳參
(1)os.Args package main1 import ( ?? ?"fmt" ?? ?"os" ) func main() { ?? ?// os.Args可以在執(zhí)行函數(shù)的時(shí)候傳遞參數(shù),但是對(duì)于-name=xxx沒(méi)有辦法將其解析 ?? ?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來(lái)說(shuō),flag.Args使用起來(lái)更加方便
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)試,主要看待測(cè)模塊在內(nèi)存于時(shí)間上的效益調(diào)試的時(shí)候只會(huì)顯示耗費(fèi)時(shí)間空間較多的代碼段.
生成調(diào)試代碼塊的文件:go run xx.exe -cpu....
使用go語(yǔ)言工具查看代碼塊所存在的問(wèn)題:go tool pprof cpu.pprof
package main import ( ?? ?"flag" ?? ?"fmt" ?? ?"os" ?? ?"runtime/pprof" ?? ?"time" ) // 一段有問(wèn)題的代碼 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)行什么測(cè)試,然后執(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ì)常用的測(cè)試函數(shù),子測(cè)試進(jìn)行了詳細(xì)的講解,對(duì)于pprof參數(shù)調(diào)優(yōu)僅僅做了個(gè)介紹,可以根據(jù)自己的興趣在網(wǎng)上找一部分pprof的例子深入學(xué)習(xí),進(jìn)行自動(dòng)化測(cè)試非常方便。
到此這篇關(guān)于Go語(yǔ)言單元測(cè)試超詳細(xì)解析的文章就介紹到這了,更多相關(guān)Go語(yǔ)言單元測(cè)試內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Go語(yǔ)言單元測(cè)試基礎(chǔ)從入門到放棄
- 一文帶你了解Go語(yǔ)言中的單元測(cè)試
- 一文詳解Go語(yǔ)言單元測(cè)試的原理與使用
- Go語(yǔ)言工程實(shí)踐單元測(cè)試基準(zhǔn)測(cè)試示例詳解
- go語(yǔ)言單元測(cè)試基準(zhǔn)測(cè)試及表驅(qū)動(dòng)測(cè)試示例詳解
- 詳解在Go語(yǔ)言單元測(cè)試中如何解決文件依賴問(wèn)題
- Go語(yǔ)言:打造優(yōu)雅數(shù)據(jù)庫(kù)單元測(cè)試的實(shí)戰(zhàn)指南
- Go語(yǔ)言單元測(cè)試的實(shí)現(xiàn)及用例
相關(guān)文章
淺析Go語(yǔ)言中的緩沖區(qū)及其在fmt包中的應(yīng)用
這篇文章主要為大家詳細(xì)介紹了Go語(yǔ)言中的緩沖區(qū)及其在fmt包中的應(yīng)用的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2024-01-01Golang基于泛化調(diào)用與Nacos實(shí)現(xiàn)Dubbo代理
這篇文章主要為大家詳細(xì)介紹了Golang如何基于泛化調(diào)用與Nacos實(shí)現(xiàn)Dubbo代理,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-04-04Go語(yǔ)言調(diào)用ffmpeg-api實(shí)現(xiàn)音頻重采樣
最近對(duì)golang處理音視頻很感興趣,對(duì)golang音視頻常用庫(kù)goav進(jìn)行了一番研究。自己寫(xiě)了一個(gè)wav轉(zhuǎn)采樣率的功能。給大家分享一下,中間遇到了不少坑,解決的過(guò)程中還是蠻有意思的,希望大家能喜歡2022-12-12Go語(yǔ)言對(duì)前端領(lǐng)域的入侵WebAssembly運(yùn)行原理
這篇文章主要為大家介紹了不安分的Go語(yǔ)言對(duì)Web?前端領(lǐng)域的入侵WebAssembly運(yùn)行原理實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07Go?mod?replace使用方法及常見(jiàn)問(wèn)題分析
這篇文章主要為大家介紹了Go?mod?replace使用方法及常見(jiàn)問(wèn)題分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08