go 基准测试 找不到函数_基于Golang做测试
本文在實(shí)習(xí)期間完成并完善,無(wú)任何公司機(jī)密,僅做語(yǔ)言交流學(xué)習(xí)之用。
持續(xù)更新。
1.Golang的單元測(cè)試
Go語(yǔ)言提供了豐富的單測(cè)功能。在Go中,我們通常認(rèn)為函數(shù)是最小的可執(zhí)行單元。本例中使用兩個(gè)簡(jiǎn)單的函數(shù):IsOdd和IsPalindrome來(lái)進(jìn)行Go單測(cè)的研究。
在VSCode中,在函數(shù)名上點(diǎn)擊右鍵,選擇“Go: Generate Unit Tests For Function"即可生成單測(cè)文件。
前往對(duì)應(yīng)的*_test.go,開始以表格化的方式填寫測(cè)試用例。這里我們每個(gè)函數(shù)都填5個(gè)測(cè)試用例:
這里name代表的是測(cè)試用例的名字,這里建議每個(gè)測(cè)試用例的名字都唯一,否則你很有可能不知道發(fā)生錯(cuò)誤的用例到底是哪個(gè)。同時(shí)我在34行和38行加入了當(dāng)測(cè)試用例不通過時(shí),輸出測(cè)試用例名字的語(yǔ)句。這樣就可以快速定位到測(cè)試不通過的測(cè)試用例。這里我們故意將第5個(gè)測(cè)試用例的want寫錯(cuò)(32767是奇數(shù),所以本應(yīng)該返回true的,也就是want應(yīng)該為true),看看測(cè)試工具是怎么報(bào)錯(cuò)的。
點(diǎn)擊函數(shù)名上面那個(gè)run test,,這樣可以開始執(zhí)行測(cè)試。run test只會(huì)運(yùn)行它所下面一行所聲明的測(cè)試函數(shù)。如果需要測(cè)試所有函數(shù)可以命令行輸入go test或者go test -v。對(duì)IsOdd的測(cè)試如下:
可以看到其實(shí)不自己添加用例輸出語(yǔ)句,FAIL的時(shí)候go的測(cè)試工具也會(huì)幫你輸出到底是哪個(gè)用例不通過。所以34行和38行的代碼并不是必須的。我們把第五個(gè)測(cè)試用例修改成正確的,再次運(yùn)行測(cè)試:
這個(gè)時(shí)候全部的測(cè)試用例就通過了,而且因?yàn)檫@是一種完全只是關(guān)心輸入輸出的測(cè)試,并不涉及到函數(shù)內(nèi)部的具體細(xì)節(jié),我們稱之為“黑盒測(cè)試”。
而其中我們還能選擇不同的日志等級(jí)輸出錯(cuò)誤信息,單測(cè)框架提供的所有日志方法都會(huì)結(jié)束測(cè)試,若只想標(biāo)記測(cè)試用例不通過,請(qǐng)使用t.Fail()。具體的日志方法如表:
方法功能
t.Log()/t.Logf() 打印標(biāo)準(zhǔn)等級(jí)日志,同時(shí)結(jié)束測(cè)試
t.Error()/t.Errorf() 打印錯(cuò)誤等級(jí)日志,同時(shí)結(jié)束測(cè)試
t.Fatal()/t.Fatalf() 打印致命等級(jí)日志,同時(shí)結(jié)束測(cè)試
2.Golang的測(cè)試覆蓋率
Go的測(cè)試覆蓋率一般指的是測(cè)試用例可以觸發(fā)函數(shù)內(nèi)的多少個(gè)分支語(yǔ)句占全部的分支語(yǔ)句的比例,在VSCode中可以以顏色區(qū)分的方式來(lái)判斷當(dāng)前的測(cè)試函數(shù)覆蓋到了哪些分支,沒有覆蓋到哪些分支。首先,在測(cè)試文件中,右鍵任意一個(gè)測(cè)試函數(shù),選擇"Go: Toggle Test Coverage In Current Package"來(lái)開始進(jìn)行測(cè)試樣例覆蓋。
這里我們將返回值應(yīng)為false的測(cè)試用例給去掉,執(zhí)行測(cè)試。測(cè)試完成后回到被測(cè)試的函數(shù)源文件中,可以發(fā)現(xiàn)被測(cè)到的分支為以墨綠色標(biāo)記,而沒有被測(cè)試到的代碼分支以紅色標(biāo)記。
將測(cè)試樣例復(fù)原后再進(jìn)行測(cè)試,可以發(fā)現(xiàn)測(cè)試覆蓋率達(dá)到了100%,同時(shí)所有的代碼都是墨綠色的了:
3.Golang的基準(zhǔn)性能測(cè)試
3.1 非并行Golang benchmark
Golang提供了測(cè)試函數(shù)運(yùn)行性能的工具,對(duì)于所有的函數(shù)來(lái)說,其性能測(cè)試函數(shù)都是在前面加Benchmark。我們還是用上面所說的兩個(gè)函數(shù),來(lái)寫一下它們的benchmark測(cè)試函數(shù)(但是我沒找到怎么一鍵生成benchmark的選項(xiàng)):
其中b.ReportAllocs()會(huì)報(bào)告這個(gè)函數(shù)的內(nèi)存使用情況(執(zhí)行一次方法要申請(qǐng)多少次內(nèi)存,每次申請(qǐng)需要申請(qǐng)多大的內(nèi)存),也可以通過指定-benchmem參數(shù)來(lái)輸出所有函數(shù)的內(nèi)存性能。
執(zhí)行測(cè)試命令(或者使用VSCode的那個(gè)run benchmark按鈕測(cè)試單個(gè)函數(shù)的benchmark):
Linux: go test -bench=. go test -bench=. -benchmem # If memory analysis info is required go test -bench=. -benchtime=3s # If benchmark test time is not 1s, use -benchtime to set it Windows: go test -bench="." go test -bench="." -benchmem # If memory analysis info is required go test -bench="." -benchtime=3s # If benchmark test time is not 1s, use -benchtime to set it測(cè)試出來(lái)的結(jié)果如下:
輸出解讀:
數(shù)據(jù)意義
BenchmarkIsOdd-8 以P=8來(lái)測(cè)試IsOdd的性能
232279942 代表在1s內(nèi)(如果沒有指定-benchtime則默認(rèn)測(cè)試時(shí)間為1s)執(zhí)行了IsOdd 232279942次
5.16 ns/op 代表每執(zhí)行一次IsOdd所花費(fèi)的時(shí)間為5.16 ns
0B/op 代表每執(zhí)行一次IsOdd所分配的內(nèi)存為0B
0 allocs/op 代表每執(zhí)行一次IsOdd申請(qǐng)分配內(nèi)存的次數(shù)為0次
我們新寫一個(gè)函數(shù),這個(gè)函數(shù)涉及到分配內(nèi)存。我們先寫一個(gè)AllocFixedArray來(lái)申請(qǐng)一個(gè)長(zhǎng)度固定的數(shù)組并循環(huán)往里面填寫數(shù)據(jù),然后再寫一個(gè)AllocMutableArray來(lái)申請(qǐng)一個(gè)長(zhǎng)度可擴(kuò)充的數(shù)組,使這兩個(gè)申請(qǐng)數(shù)組的長(zhǎng)度相同,觀察它們的性能:
首先對(duì)它們做單測(cè),確保代碼運(yùn)行上沒有問題,由于這里沒有邏輯判斷,所以有一個(gè)測(cè)試用例就夠了:
確認(rèn)結(jié)果正確后,寫出它們對(duì)應(yīng)的Benchmark函數(shù):
然后可以點(diǎn)擊run benchmark一個(gè)個(gè)測(cè),或者直接全部函數(shù)都測(cè)一下,這里選擇全部測(cè)試:
可以發(fā)現(xiàn),使用make聲明固定長(zhǎng)度的內(nèi)存是沒有allocs的,而append底部會(huì)在數(shù)組長(zhǎng)度不足的時(shí)候?qū)?shù)組進(jìn)行擴(kuò)充,所以會(huì)有內(nèi)存的申請(qǐng)。并且我們可以看到,append因?yàn)榈讓由暾?qǐng)了內(nèi)存,性能大大下降,AllocMutableArray的執(zhí)行時(shí)間是AllocFixedArray的差不多25倍。這個(gè)也提示我們,盡量要對(duì)數(shù)組的大小有一個(gè)預(yù)先的估計(jì),并申請(qǐng)好一個(gè)capacity比較接近最大上限的數(shù)組。
3.2 并行Golang benchmark
測(cè)試的時(shí)候同樣可以使用并行的方法去并發(fā)測(cè)試指定的時(shí)間內(nèi)能執(zhí)行多少次該方法,其基本語(yǔ)法為:
b我們?cè)囋噷?zhí)行比較慢的AllocMutableArray()來(lái)并發(fā)處理,看看會(huì)如何:
執(zhí)行基準(zhǔn)測(cè)試,得到結(jié)果:
我們可以發(fā)現(xiàn),在P=8并發(fā)執(zhí)行AllocMutableArray之后,執(zhí)行時(shí)間從73.6ns/op降到了14.6ns/op。
3.3 Golang benckmark中的計(jì)時(shí)器
假設(shè)說一個(gè)函數(shù)在執(zhí)行之前,要先執(zhí)行一些外部的初始化操作。而我們?nèi)绻趃o test里面制定了-benchtime選項(xiàng),它記錄的將會(huì)是整個(gè)Benchmark函數(shù)的運(yùn)行時(shí)間。所以我們需要有一種操縱定時(shí)器的方法,來(lái)獲得整個(gè)服務(wù)精確的運(yùn)行時(shí)間。假設(shè)我們的IsOdd,它在執(zhí)行之前需要睡眠100毫秒,那么我們就可以在執(zhí)行完睡眠之后,使用重置計(jì)數(shù)器的方法開始計(jì)時(shí)。
OK,測(cè)試用的總共時(shí)間為3.166s。然后我們加上不對(duì)初始化進(jìn)行計(jì)時(shí)的代碼(取消16、18行的注釋),重新測(cè)一次:
可以看到測(cè)試時(shí)間有顯著的下降,這說明使用b.StopTimer()后,沒有將初始化的時(shí)間算在總測(cè)試時(shí)間內(nèi)。
方法功能
b.StartTimer() 復(fù)原或打開計(jì)時(shí)器,當(dāng)Benchmark執(zhí)行前會(huì)首先執(zhí)行b.StartTimer()
b.StopTimer() 暫停當(dāng)前計(jì)時(shí)器
b.ResetTimer() 重置當(dāng)前計(jì)時(shí)器的值,go官方說該函數(shù)在計(jì)時(shí)器運(yùn)行時(shí)無(wú)效,但我試了一下是有效的。建議先b.StopTimer()后再調(diào)用此方法,最后再b.StartTimer()
StartTimer, StopTimer和ResetTimer其實(shí)就相當(dāng)于我們常用的秒表的三個(gè)按鈕:開始,暫停和復(fù)位。當(dāng)Benchmark函數(shù)執(zhí)行之前,就會(huì)自動(dòng)調(diào)用StartTimer。而ResetTimer函數(shù)生效的前提是必須先調(diào)用StopTimer。通過這樣的控制,就可以控制基準(zhǔn)測(cè)試的計(jì)時(shí)器,防止一些無(wú)關(guān)部分的時(shí)間被測(cè)算進(jìn)來(lái)了。
3.4 Golang benchmark的Profile(性能分析)
golang的benchmark提供了一種輸出性能分析的工具,在測(cè)試benchmark命令的前提下,加上參數(shù)即可,下面提供了三種獲取全部基準(zhǔn)測(cè)試函數(shù)不同性能的指令:
test -bench當(dāng)獲得這些性能文件之后,也會(huì)相應(yīng)地留下一個(gè)***.test為文件名的可執(zhí)行文件。為什么要留下這個(gè)測(cè)試時(shí)候生成的臨時(shí)程序呢?在生成profile文件的時(shí)候,為了減少冗余,生成的文件全部都是不含符號(hào)信息的,也就是說其實(shí)并沒有記錄性能條目對(duì)應(yīng)的是哪個(gè)函數(shù)的性能,所以需要有一個(gè)這樣的副本程序來(lái)記錄符號(hào)信息。
當(dāng)我們獲得這些文件之后,使用go自帶的pprof來(lái)查看這些文件所表示的含義,其中-nodecount=10表示僅顯示前10個(gè)最耗性能的條目:
=其中***為根據(jù)實(shí)際需要所替換的字符串,一個(gè)名為go-learning的程序的cpu占用情況分析如下圖:
可以看到,IsPalindrome的占用時(shí)間排第2位,僅次于gc。所以我們可以著手去從這個(gè)函數(shù)進(jìn)行優(yōu)化。
4.Golang的Example測(cè)試(樣例測(cè)試)
樣例測(cè)試比較像平時(shí)在一些算法刷題平臺(tái)(比如LeetCode)的題目的一些例子,樣例測(cè)試以Example打頭,其邏輯也很簡(jiǎn)單,就是使用fmt.Println輸出該測(cè)試用例的返回結(jié)果,然后在函數(shù)體的末尾使用如圖的注釋,一一對(duì)應(yīng)每個(gè)fmt.Println的輸出:
17行的output首字母大寫小寫均可。
如果13~16行輸出的結(jié)果和18~21行的結(jié)果相對(duì)應(yīng),go test就會(huì)PASS,否則就會(huì)FAIL,并打印出實(shí)際輸出和期望輸出。
5.Go的Mock方法
5.1 Mock的簡(jiǎn)介
mock,中文譯名為“模仿,假的”,顧名思義就是構(gòu)建一個(gè)模擬對(duì)象,來(lái)替換掉一些需要在特定環(huán)境下觸發(fā)的服務(wù),使其可以在不修改原服務(wù)的前提下達(dá)到測(cè)試的目的。本文介紹一種是基于gomonkey的函數(shù)/變量Mock方法。
5.2 基于gomonkey的函數(shù)Mock方法
在使用gomonkey之前,我們要先安裝它,輸入命令:
go get github.com/agiledragon/gomonkey并在開頭import該包:
import假設(shè)我們有一個(gè)函數(shù)IsRest,當(dāng)調(diào)用這個(gè)函數(shù)的時(shí)候,程序會(huì)判斷一下現(xiàn)在的時(shí)間是否已經(jīng)是下午5點(diǎn)之后,如果是,就返回nil,表示現(xiàn)在是下班時(shí)間了。否則返回非nil值,表示現(xiàn)在還沒到下班時(shí)間。我們先寫出這個(gè)函數(shù):
那我們測(cè)試的時(shí)候肯定不可能等到5點(diǎn)再去測(cè)這個(gè)函數(shù)吧?否則這測(cè)試不就沒法做了。這個(gè)時(shí)候我們先生成它的單測(cè)函數(shù),然后施加mock:
其中,108行~114行是對(duì)IsRest進(jìn)行mock的方法,ApplyFunc指的是對(duì)函數(shù)進(jìn)行Mock,第二個(gè)參數(shù)就是要使用的Mock方法。
那我們來(lái)執(zhí)行測(cè)試:
說明在執(zhí)行測(cè)試用例的時(shí)候,gomonkey成功地把IsRest方法給mock掉了。
6. 總結(jié)
Go語(yǔ)言本身提供了豐富的單元測(cè)試和性能測(cè)試方法,但是在提供Mock方法上還是略有不足。本文從Gomock, Gomonkey和GoStub出發(fā),總結(jié)了一些創(chuàng)建Mock對(duì)象的方法。如果對(duì)于Go測(cè)試有進(jìn)一步興趣的,可以去了解GoConvey,GoMonkey,GoStub和GoMock的教程,下面列出了一個(gè)作者寫的關(guān)于這四個(gè)測(cè)試工具的文章,供讀者參考:
GoConvey框架使用指南
https://www.jianshu.com/p/633b55d73ddd?www.jianshu.comGoMock框架使用指南
https://www.jianshu.com/p/f4e773a1b11f?www.jianshu.comGoStub框架使用指南
https://www.jianshu.com/p/70a93a9ed186?www.jianshu.comMonkey框架使用指南
Monkey框架使用指南?www.jianshu.com總結(jié)
以上是生活随笔為你收集整理的go 基准测试 找不到函数_基于Golang做测试的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 修复处女膜手术多少钱啊?
- 下一篇: python菜鸟教程split_Pyth
