Go36-23,24,25-單元測試
單元測試
對於程式或軟體的測試分很多種,比如:單元測試、API測試、整合測試、灰度測試等。這裡主要針對單元測試進行講解。
Go 語言是一門很重視程式測試的語言,它不但自帶了testing包,還有專門用於程式測試的命令go test。要想真正用好一個工具,就需要了解它的核心邏輯。
測試原始碼檔案
單元測試,又稱程式設計師測試。就是程式設計師程式設計師們本該做的自我檢查的工作之一。
我們可以為Go程式編寫3類測試:
- 功能測試(test)
- 基準測試(benchmark),也稱效能測試
- 示例測試(example)
前兩類測試,從名稱上就應該可以猜到它們的用途。示例測試,嚴格來講也是一種功能測試,只不過它更關注程式打印出來的內容。
一般情況下,一個測試原始碼檔案只會針對於某個命令原始碼檔案,或庫原始碼檔案做測試,所以一般並且也應該把他們放在同一個程式碼包內。
測試原始碼檔案的主名稱應該以被測試原始碼檔案的主名稱為簽到,並且必須以“_test”為字尾。
每個測試原始碼檔案都必須至少包含一個測試函式。並且,從語法上講,每個測試原始碼檔案中,都可以包含用來做任何一類測試的測試函式,就是說把上面說的3類測試函式都塞進去也沒問題,只要把控好測試函式的分組和數量就可以了。
我們可以依據這些測試函式針對的不同程式實體,把他們分成不同額邏輯組。並且利用註釋以及幫助類的變數或函式來做分割。同時,還可以依據被被測試的檔案中程式實體的先後順序來安排測試原始碼檔案中測試函式的順序。
測試函式的名稱和簽名
對於測試函式的名稱和簽名,Go語言也有明文的規定:
- 功能測試函式,名稱必須以Test為字首,並且只有一個*testing.T型別的引數。
- 效能測試函式,名稱必須以Benchmark為字首,並且只有一個*testing.B型別的引數。
- 示例測試函式,名稱必須以Example為字首,對引數沒有強制規定。
go test的主要流程
前面兩小節的內容,其實是go test命令的基本規則。只有測試原始碼檔案的名稱對了,測試函式的名稱和簽名也對了,在執行go test命令的時候,其中的測試程式碼才會被執行。接著講主要流程。
當go test命令在開始執行時,會先做一下準備工作,比如:確定內部需要用到的命令,檢查指定的程式碼包或原始碼檔案的有效性,以及判斷我們給予的標記是否合法,等等。
在準備工作完成後,go test命令就會針對每個被測試程式碼包,依次的進行構建、執行包中符合要求的測試函式,清理臨時檔案,列印測試結果。這就是通常情況下的主要測試流程。
這裡的“依次”二字,對於每個被測試程式碼包,go test命令會序列地執行測試流程中的每個步驟。但是為了加快測試速度,通常會併發的對多個被測試程式碼包進行功能測試。但是在最後列印測試結果的時候,會依照給定的順序逐個列印。
另一個方面,由於併發的測試會讓效能測試的結果存在偏差,所以效能測試一般都是序列進行的。具體說就是在所有構建步驟都做完之後,go test命令才會真正地開始進行效能測試。並且下個一個程式碼包的效能測試的進行,會等到上一個程式碼包的效能測試的結果列印完成才會開始,並且效能測試函式的執行也都會是序列的。
功能測試
先給出示例,然後主要講測試規則和流程
功能測試示例
這裡把示例單獨給出,首先是命令原始碼檔案:
// demo.go package main import ( "errors" "flag" "fmt" ) var name string func init() { flag.StringVar(&name, "name", "bobody", "Your Name:") } func main() { flag.Parse() greeting, err := hello(name) if err != nil { fmt.Printf("ERROR: %s\n", err) return } fmt.Println(greeting, introduce()) } func hello(name string) (string, error) { if name == "" { return "", errors.New("empyt name") } return fmt.Sprintf("Hello %s.", name), nil } func introduce() string { return "Welcome" }
然後是測試原始碼檔案,這裡對檔名和函式名稱都是有要求的。看函式名稱這裡都是功能測試函式:
// demo_test.go package main import ( "fmt" "testing" ) func TestHello(t *testing.T) { var name string greeting, err := hello(name) if err == nil { t.Errorf("The error is nil, but it should not be. (name=%q)", name) } if greeting != "" { t.Errorf("Nonempty greeting, but it should not be. (name=%q)", name) } name = "Clark" greeting, err = hello(name) if err != nil { t.Errorf("The error is not nil, but it should be. (name=%q)", name) } if greeting == "" { t.Errorf("Empty greeting, but it should not be. (name=%q)", name) } expected := fmt.Sprintf("Hello %s.", name) if greeting != expected { t.Errorf("greeting: %q, expected: %q, not same.", greeting, expected) } t.Logf("The expected greeting is %q.\n", expected) } func TestIntroduce(t *testing.T) { intro := introduce() expected := "Welcome" if intro != expected { t.Errorf("intro: %q, expected: %q, not same", intro, expected) } t.Logf("The expected introduce is %q.\n", expected) }
功能測試的結果
先來看下面的測試命令和結果:
PS H:\Go\src> go test Go36\article23\example01 okGo36/article23/example010.051s PS H:\Go\src>
在這裡,輸入了命令go test Go36\article23\example01
,表示想對這個匯入路徑的程式碼包進行測試。另外go test命令,預設執行當前目錄下以_test.go結尾的測試檔案,所以可以不帶後面的引數。
命令的下面一行,就是此次測試的簡要結果。簡要結果有三塊內容:
- 最左邊的ok表示此次測試成功
- 中間的內容,顯示了被測程式碼包的匯入路徑
- 最右邊,展現了此次對該程式碼包測試所耗的時間
快取
對於最右邊的時間,有時候可能是這樣的:
okGo36/article23/example01(cached)
這表明,由於測試程式碼與被測試程式碼都沒有任何變動,所以go test命令直接把之前快取測試成功的結果打印出來了。關於這個快取,可以執行下面的命令來檢視快取目錄的路徑:
go env GOCACHE
可能windows系統上沒有快取?我在win10系統上貌似是沒有快取。go env裡也沒有GOCACHE這個環境變數。嘗試手動去系統里加上這個變數,再用go env也是顯示不出來,系統重啟也試過了 快取的資料可以正確的反應出測試時的各種原始碼檔案、構建環境、編譯器選項等情況。一旦有任何變動,快取的資料就會失效,go test命令就會再次真正地執行操作。go命令會定期地刪除最近未使用的快取資料,不過也是可以手動刪除所有快取資料的,命令如下:
go clean -cache
上面的命令清除的是go命令所有的快取,對於測試成功的結果,go命令也會快取。可以只刪除所有測試結果的快取,而不刪除任何構建結果的快取。命令如下:
go clean -testcache
這裡的命令也有問題,列印go clen的幫助如下:
PS H:\Go\src\Go36> go clean --help usage: clean [-i] [-r] [-n] [-x] [build flags] [packages]
這裡是網上查到的方法:顯示禁用快取的慣用方法是使用-count=1標誌。就是作為引數加到go test命令後面。
總之,不用太在意快取資料的存在,因為這並不會妨礙go test命令列印正確的測試結果。
測試失敗
如果功能測試的函式的引數名稱為t,那麼呼叫t.Fail方法,可以使測試失敗。在之前的測試程式碼的最後,再新增一個功能測試函式TestFail,具體如下:
func TestFail(t *testing.T) { t.Fail() t.Log("測試失敗...") }
現在再執行測試,就會顯示測試失敗,具體如下:
PS G:\Steed\Documents\Go\src\Go36\article24\example01> go test --- FAIL: TestFail (0.00s) demo_test.go:43: 測試失敗... FAIL exit status 1 FAILGo36/article24/example010.115s PS G:\Steed\Documents\Go\src\Go36\article24\example01>
上面的內容顯示,TestFail函式的測試失敗。
對於失敗測試的結果,是不會進行快取的,所以每次測試都是全新的結果。
這裡還有一個方法:t.FailNow()。
與t.Fail()不同,在t.FailNow()執行後,當前函式會立即終止執行。t.FailNow方法只是終止它所在的函式,不會影響到其他函式的處理。但是當前函式執行到t.FailNow()之後,後面的程式碼就沒有執行的的機會了。如果把上面例子中的t.Fail()替換成t.FailNow(),則不會列印測試日誌,因為t.Log沒有機會執行。
測試日誌
如果測試失敗了,那麼go test會將導致失敗的測試函式中的常規測試日誌一併打印出來,就是t.Log方法呼叫的內容。
t.Log方法以及t.Logf方法,就是列印常規的測試日誌。只有在測試失敗的時候才當測試成功的時候,不列印這類日誌。預設在測試成功的時候不列印這類日誌,不過可以加上-v引數在成功時也列印測試日誌。
t.Error 和 t.Errorf如果想在測試失敗的通常列印失敗測試日誌,那麼可以直接呼叫t.Error或t.Errorf方法。相當於t.Log或t.Logf和t.Fail方法的連續呼叫。
t.Fatal 和 t.Fatalf這兩個方法的作用是在列印失敗錯誤日誌後立即終止當前測試函式的執行並宣告失敗。就是相當於呼叫了t.FailNow方法。
效能測試
效能測試與功能測試的結果有很多相似的地方,先把示例程式碼準備好,然後關注效能測試結果裡的特殊之處。
測試示例
先準備用來做效能測試的包,這裡用不同的方法來實現斐波那契數列。一個用遞迴方法,效率比較差,還有一個是for迴圈實現的,邏輯稍微複雜一點但是效率更高:
// Go36/article24/example02/fibonacci/fibonacci.go package fibonacci func FibRescuvie (x uint) uint64 { if x > 93 { return 0 }// uint64最多可以存第93個數,再高就溢位了 switch x { case 0: return 0 case 1, 2: return 1 default: return FibRescuvie(x-2) + FibRescuvie(x-1) } } func FibForLoop (x uint) uint64 { first := uint64(1) second := uint64(1) var r uint64 for i := uint(3); i <= x; i++ { r = first + second first, second = second, r } return r }
然後是效能測試的程式碼:
// Go36/article24/example02/fibonacci/fibonacci_test.go package fibonacci import "testing" func BenchmarkFibRescuvie(b *testing.B) { for i := 0; i < b.N; i++ { FibRescuvie(40) } } func BenchmarkFibForLoop(b *testing.B) { for i := 0; i < b.N; i++ { FibForLoop(40) } }
接著就來執行測試:
PS H:\Go\src> go test -bench . -run=^$ Go36/article24/example02/fibonacci goos: windows goarch: amd64 pkg: Go36/article24/example02/fibonacci BenchmarkFibRescuvie-83408665433 ns/op BenchmarkFibForLoop-810000000019.2 ns/op PASS okGo36/article24/example02/fibonacci4.484s PS H:\Go\src>
效能測試的命令
先說執行測試的命令,上面的命令加了2個標記:
-bench .
,必須要加上這個標記,才會進行效能測試。該標記的值是個點(.)表示要執行的效能測試函式,這裡用了點,表示執行任意名稱的效能測試函式。還可以這麼寫-bench="."
、-bench FibRescuvie
、-bench=FibRescuvie
。
-run=^$
,這個標記由於表明需要執行哪些功能測試函式,同樣也是以函式名稱為依據。這裡標記的值是^$,分別是正則表示式的開頭和結束符,就是一個空的名稱。也就是沒有任何函式名稱會符合要求,就是不執行任何的功能測試函式。如果不加-run標記,那麼就會執行程式碼包中所有的功能測試函式。
這裡的兩個標記的值都是正則表示式,並且也只能以正則表示式為值。
效能測試結果
關於結果,重點說下這行,重新貼出來:
BenchmarkFibRescuvie-83408665433 ns/op
CPUBenchmarkFibRescuvie-8被稱為單個性能測試的名稱。它表示命令執行了效能測試函式BenchmarkFibRescuvie,並且當時所有的最大P數量為8。這裡的P是processor的縮寫,最大P數量相當於可以同時執行goroutine的邏輯CPU的最大個數(確認了一下,機器確實是8核的)。這裡的邏輯CPU,也可以被稱為CPU核心,但它並不等同於計算機中真正的CPU核心,只是Go語言執行時系統內部的一個概念,代表著它同時執行goroutine的能力。一臺計算機的CPU核心的個數,意味著它能在同一時刻執行多少條程式指令,代表著它並行處理程式指令的能力。可以通過呼叫runtime.GOMAXPROCS
函式改變最大P數量,也可以在執行go tes命令的時候,加入標記-cpu來設定一個最大P數量的列表,以供命令在多次測試時使用。
執行次數中間的數字是被測試函式被執行的實際次數。go test命令在執行效能測試函式的時候會給它一個正整數,例子中就是b.N。我們應該在測試函式中配合著編寫程式碼,比如:
for i := 0; i < b.N; i++ { FibForLoop(40) }
它會先迭代b.N次的迴圈中呼叫被測試函式。go test命令先嚐試把b.N設定為1,然後執行測試函式。如果測試函式的執行時間沒有超過上限,上限預設為1秒,那麼命令就會改大b.N的值,然後再次執行測試函式,如此往復,知道這個時間大於或等於上限為止。可以簡稱該值為執行次數,但是它指的是被測試函式的執行次數,而不是效能測試函式的執行次數。
執行平均時間最後是遊標的數字,表明單次執行被測試函式的平均耗時,這裡的ns是納秒。這個值其實就是將測試的總時間除以執行次數而得出的。
go test命令
介紹幾個可以被go test命令接受的重要標記,進一步說明功能測試和效能測試在不同條件下的測試流程。
回顧G-P-M模型
上篇的go test命令提到過一個-cpu標記,它是用來設定執行最大P數量的列表的。
這裡的P是process的縮寫,在講go語句(就是goroutine)的時候說過。每個process都是一個可承載若干個G,並且能夠使這些G適時地與M進行對接並得到真正執行的中介。正式由於P的存在,G和M才可以呈現出多對多的關係,並能夠及時、靈活地進行組合和分離。
這裡的G是goroutine的縮寫,可以被理解為Go語言自己實現的使用者級執行緒。
這裡的M是machine的縮寫,代表著系統級執行緒,或者說作業系統核心級別的執行緒。
Go語言併發程式模型中的P,正式goroutine的數量能夠數十萬計的關鍵所在。P的數量意味著Go程式背後執行時系統中,會有多少個由於承載可執行的G的佇列存在。每一個佇列都相當於一條流水線,源源不斷地把可執行的G輸送給空閒的M,並使兩者對接。一旦對接完成,被對接的G就真正的執行在作業系統的核心級執行緒之上了。每條流水線之間雖然有聯絡,但都是獨立運作的。
因此,最大P數量就代表著Go語言執行時系統同時執行goroutine的能力,也可以被視為其中邏輯CPU的最大個數。go test命令的-cpu標記就是用於設定這個最大個數的。在預設情況下,最大P數量就等於當前計算機CPU核心的實際數量。也可以大於或者小於實際數量,如此可以在一定程度上模擬擁有不同CPU核心數的計算機。
設定-cpu標記的值
標記-cpu應該是一個正整數的列表,用逗號分隔的多個整數字面量。
針對值中的每一個正整數,go test會先設定最大P數量為該數值,然後再執行測試函式。如果測試函式有多個,go test會先以所有-cpu引數值去執行第一個測試函式,然後再用同樣的方式執行後面的測試函式。
go test執行測試函式時,關於-cpu標記的具體步驟。不論是否設定了-cpu標記,go test在進行準備工作的時候,會讀取-cpu標記的值,並把它轉換為一個int為元素型別的切片。如果沒有-cpu標記,那麼切片就只包含一個元素,值是最大P數量的預設值。在準備某個測試函式的時候,無論該函式是功能測試函式還是效能測試函式,go test會迭代切片的值。在每次迭代的時候,先依據當前元素的值設定最大P數量,然後再去執行測試函式。
這裡注意,雖然上一小節說可以大於CPU核心的實際數量。但是最好不要超過,因為一旦超出計算機實際的並行處理能力,Go程式在效能上就無法再獲得到顯著的提升了。而且為了管理過多的P,系統還會耗費額外的計算資源。
-count標記
go test每一次對效能測試函式的執行,對一個探索的過程。它會在測試函式的執行時間上限不變的前提下,嘗試找到被測試程式的最大執行次數。在這個過程中,效能測出函式可能會被執行多次。可以把這樣一個探索的過程稱為探索式執行 。
這裡有一個標記-count,就是專門用於重複執行測試函式的。它的值必須大於或等於0,預設值為1。如果設定了-count,對於每一個測試函式,命令都會在設定不同的條件下(比如不同的最大P數量)分別重複執行若干次。所以如果配合上-cpu標記,那麼執行的次數就是-cpu切片裡元素的數量和-count數值的乘積,這個是對於功能測試的執行次數。如果是效能測試,這個乘積就是測試函式執行探索式執行的次數,實際次數就要再乘以一次探索式執行中測試函式實際執行的次數了。
下面的結果是設定了-cpu和-count之後的測試結果,可以看下期中的執行次數和順序:
PS G:\Steed\Documents\Go\src\Go36\article24\example02\fibonacci> go test -bench . -cpu 1,2,3 -count 2 goos: windows goarch: amd64 pkg: Go36/article24/example02/fibonacci BenchmarkFibRescuvie2642132400 ns/op BenchmarkFibRescuvie2639623600 ns/op BenchmarkFibRescuvie-22637135800 ns/op BenchmarkFibRescuvie-22637134900 ns/op BenchmarkFibRescuvie-32632637750 ns/op BenchmarkFibRescuvie-32630628650 ns/op BenchmarkFibForLoop5000000031.0 ns/op BenchmarkFibForLoop5000000031.1 ns/op BenchmarkFibForLoop-25000000030.7 ns/op BenchmarkFibForLoop-25000000031.3 ns/op BenchmarkFibForLoop-35000000031.0 ns/op BenchmarkFibForLoop-35000000031.3 ns/op PASS okGo36/article24/example02/fibonacci22.063s PS G:\Steed\Documents\Go\src\Go36\article24\example02\fibonacci>
-parallel標記
在執行go test命令的時候,可以追加-parallel標記。該標記的作用是,設定同一個被測試程式碼包中的功能測試函式的最大併發執行數,預設值是測試執行時的最大P數量。
對於功能測試,為了加快測試速度,通常會併發的測試多個被測程式碼包。但是預設情況下,同一個程式碼包這的過個功能測試函式是序列的執行的。除非在一些功能測試函式中顯式的呼叫t.Parallel方法。實際也很簡單,在函式開頭加一行:
func TestIntroduce(t *testing.T) { t.Parallel()// 其它不變,就加這一句 intro := introduce() expected := "Welcome" if intro != expected { t.Errorf("intro: %q, expected: %q, not same", intro, expected) } t.Logf("The expected introduce is %q.\n", expected) }
這個時候,這些包含了t.Parallel方法呼叫的功能測試函式就會被go test併發的執行,而併發執行的最大數量是用-parallel標記決定的。不過同一個功能測試函式的多次執行之間一定是序列的。
另外還可以在一個功能測試函式裡,併發的起多個子測試。下面是一段程式碼的示例:
// 這個是要展示的函式 func TestGetPrimesParallel(t *testing.T) { for _, max := range []int{1, 2, 3, 4, 5} { max := max * 200 // 這些子測試會被併發地執行, // 並且只有它們都執行完畢之後當前的測試函式才會執行完成。 // 當前的測試函式並不會與其他測試函式一起被併發的執行。 t.Run(fmt.Sprintf("TestGetPrimesWith%d", max), func(t *testing.T) { t.Parallel() primes := GetPrimes(max) err := comparePrimes(primes) if err != nil { t.Error(err) } else { t.Logf("The primes less than %d are all correct.", max) } } ) } } // 下面的切片和一個函式也是測試原始碼檔案裡要用到的部分 var expectedPrimes = []int{ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997, } func comparePrimes(primes []int) (err error) { for i, prime := range primes { expectedPrime := expectedPrimes[i] if prime != expectedPrime { err = fmt.Errorf( "%dth prime number %d is not the expected value %d", i, prime, expectedPrime) break } } return } // 下面是命令原始碼檔案裡被測試的函式 // GetPrimes 用於獲取小於或等於引數max的所有質數。 // 本函式使用的是愛拉託遜斯篩選法(Sieve Of Eratosthenes)。 func GetPrimes(max int) []int { if max <= 1 { return []int{} } marks := make([]bool, max) var count int squareRoot := int(math.Sqrt(float64(max))) for i := 2; i <= squareRoot; i++ { if marks[i] == false { for j := i * i; j < max; j += i { if marks[j] == false { marks[j] = true count++ } } } } primes := make([]int, 0, max-count) for i := 2; i < max; i++ { if marks[i] == false { primes = append(primes, i) } } return primes }
上面的程式碼中,由於測試函式本身,就是TestGetPrimesParallel這個函式裡沒有顯式的呼叫t.Parallel方法,所以這個測試函式是不會與別的測試函式一起被併發的執行的。併發執行的只是裡面的那些顯式的呼叫了t.Parallel方法的子測試。
另外,這個標記對效能測試是無效的。對於效能測試來說,也是可以併發進行的,不過機制上不同。這涉及了b.RunParallel方法、b.SetParallelism方法和-cpu標記的聯合運用,這裡沒有展開講。要進一步瞭解,建議去看testing包的文件了:https://golang.google.cn/pkg/testing/
效能測試函式中的計時器
如果去檢視testing包的文件,在裡面會發現其中的testing.B型別有這麼幾個指標方法:
- func (b *B) StartTimer()
- func (b *B) StopTimer()
- func (b *B) ResetTimer()
這些方法都是用於操作當前效能測試函式專屬的計時器的。這三個方法在開始記錄、停止記錄以及重新記錄執行時間的同時,也會對堆記憶體分配位元組數和分配次數的記錄起到相同的作用。
看下面的例子,在之前的效能測試函式之前加了4行程式碼:
func BenchmarkFibForLoop(b *testing.B) { b.StopTimer() time.Sleep(time.Millisecond * 300)// 模擬一個費時的操作 var n uint = 40 b.StartTimer() for i := 0; i < b.N; i++ { FibForLoop(n) } }
這裡,先停止了計時器。然後time.Sleep,接著給n賦值,這裡其實是模擬了一個比較耗時的額外操作,為的是獲取到n的值。然後又啟動了計時器。這裡的意義是在為n賦值的這個過程中,計時器是停止的。所以這部分時間不會包含在測試函式的執行時間中,這樣就避免了這個耗時的賦值過程對測試結果的影響。這裡被測試程式在執行時間限制內,對被測出函式的最大執行次數不會受到這個段停止計時的時間的影響。可以註釋掉b.StopTimer()和b.StartTimer()比較效能測試的結果,主要看執行次數:
PS G:\Steed\Documents\Go\src\Go36\article25\example01\fibonacci> go test -bench FibForLoop goos: windows goarch: amd64 pkg: Go36/article25/example01/fibonacci BenchmarkFibForLoop-43000000040.5 ns/op PASS okGo36/article25/example01/fibonacci6.376s PS G:\Steed\Documents\Go\src\Go36\article25\example01\fibonacci> go test -bench FibForLoop goos: windows goarch: amd64 pkg: Go36/article25/example01/fibonacci BenchmarkFibForLoop-45000000033.9 ns/op PASS okGo36/article25/example01/fibonacci3.366s PS G:\Steed\Documents\Go\src\Go36\article25\example01\fibonacci>
下面的結果是沒有註釋程式碼時執行的結果,和之前測試的結果是一樣的。上面的結果是註釋掉b.StopTimer()和b.StartTimer()之後執行的,由於每次為n賦值都會耗費額外的時間,並且計算在測試函式的執行時間裡了,所以單次執行的時間長了,總的執行次數也就變少了。
在效能測試函式中,可以通過b.StopTimer方法和b.StartTimer方法的聯合運用,去除掉任何一段程式碼的執行時間。還有一個b.ResetTimer方法,靈活性就要差一些,值能用於去除在呼叫它之前的那些程式碼的執行時間。不過,無論在呼叫它的時候,計時器是否正在運轉,它都可以起作用。
其它標記
最後還有2個標記是通過思考題給出的,所以沒有詳細的介紹:
- -benchmem : 輸出效能測試的記憶體分配統計資訊。
- -benchtime : 用於指定效能測試的探索式測試執行時間上限
命令示例:
$ go test -bench . -benchmem -benchtime 10s