溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Go語(yǔ)言怎么通過(guò)測(cè)試保證質(zhì)量

發(fā)布時(shí)間:2022-08-24 14:09:10 來(lái)源:億速云 閱讀:119 作者:iii 欄目:開(kāi)發(fā)技術(shù)

本篇內(nèi)容介紹了“Go語(yǔ)言怎么通過(guò)測(cè)試保證質(zhì)量”的有關(guān)知識(shí),在實(shí)際案例的操作過(guò)程中,不少人都會(huì)遇到這樣的困境,接下來(lái)就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

單元測(cè)試

在開(kāi)發(fā)完一個(gè)功能后,你可能會(huì)直接把代碼合并到代碼庫(kù),用于上線或供其他人使用。但這樣是不對(duì)的,因?yàn)槟氵€沒(méi)有對(duì)所寫(xiě)的代碼進(jìn)行測(cè)試。沒(méi)有經(jīng)過(guò)測(cè)試的代碼邏輯可能會(huì)存在問(wèn)題:如果強(qiáng)行合并到代碼庫(kù),可能影響其他人的開(kāi)發(fā);如果強(qiáng)行上線,可能導(dǎo)致線上 Bug、影響用戶使用。

什么是單元測(cè)試

顧名思義,單元測(cè)試強(qiáng)調(diào)的是對(duì)單元進(jìn)行測(cè)試。在開(kāi)發(fā)中,一個(gè)單元可以是一個(gè)函數(shù)、一個(gè)模塊等。一般情況下,你要測(cè)試的單元應(yīng)該是一個(gè)完整的最小單元,比如 Go 語(yǔ)言的函數(shù)。這樣的話,當(dāng)每個(gè)最小單元都被驗(yàn)證通過(guò),那么整個(gè)模塊、甚至整個(gè)程序就都可以被驗(yàn)證通過(guò)。

單元測(cè)試由開(kāi)發(fā)者自己編寫(xiě),也就是誰(shuí)改動(dòng)了代碼,誰(shuí)就要編寫(xiě)相應(yīng)的單元測(cè)試代碼以驗(yàn)證本次改動(dòng)的正確性。

Go 語(yǔ)言的單元測(cè)試

雖然每種編程語(yǔ)言里單元測(cè)試的概念是一樣的,但它們對(duì)單元測(cè)試的設(shè)計(jì)不一樣。Go 語(yǔ)言也有自己的單元測(cè)試規(guī)范,下面我會(huì)通過(guò)一個(gè)完整的示例為你講解,這個(gè)例子就是經(jīng)典的斐波那契數(shù)列。

斐波那契數(shù)列是一個(gè)經(jīng)典的黃金分隔數(shù)列:它的第 0 項(xiàng)是 0;第 1 項(xiàng)是 1;從第 2 項(xiàng)開(kāi)始,每一項(xiàng)都等于前兩項(xiàng)之和。所以它的數(shù)列是:0、1、1、2、3、5、8、13、21……

說(shuō)明:為了便于總結(jié)后面的函數(shù)方程式,我這里特意寫(xiě)的從第 0 項(xiàng)開(kāi)始,其實(shí)現(xiàn)實(shí)中沒(méi)有第 0 項(xiàng)。

根據(jù)以上規(guī)律,可以總結(jié)出它的函數(shù)方程式。

F(0)=0

F(1)=1

F(n)=F(n - 1)+F(n - 2)

有了函數(shù)方程式,再編寫(xiě)一個(gè) Go 語(yǔ)言函數(shù)來(lái)計(jì)算斐波那契數(shù)列就比較簡(jiǎn)單了,代碼如下:

ch28/main.go

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

也就是通過(guò)遞歸的方式實(shí)現(xiàn)了斐波那契數(shù)列的計(jì)算。

Fibonacci 函數(shù)已經(jīng)編寫(xiě)好了,可以供其他開(kāi)發(fā)者使用,不過(guò)在使用之前,需要先對(duì)它進(jìn)行單元測(cè)試。你需要新建一個(gè) go 文件用于存放單元測(cè)試代碼。剛剛編寫(xiě)的 Fibonacci 函數(shù)在ch28/main.go文件中,那么對(duì) Fibonacci 函數(shù)進(jìn)行單元測(cè)試的代碼需要放在ch28/main_test.go中*,*測(cè)試代碼如下:

ch28/main_test.go

func TestFibonacci(t *testing.T) {
   //預(yù)先定義的一組斐波那契數(shù)列作為測(cè)試用例
   fsMap := map[int]int{}
   fsMap[0] = 0
   fsMap[1] = 1
   fsMap[2] = 1
   fsMap[3] = 2
   fsMap[4] = 3
   fsMap[5] = 5
   fsMap[6] = 8
   fsMap[7] = 13
   fsMap[8] = 21
   fsMap[9] = 34
   for k, v := range fsMap {
      fib := Fibonacci(k)
      if v == fib {
         t.Logf("結(jié)果正確:n為%d,值為%d", k, fib)
      } else {
         t.Errorf("結(jié)果錯(cuò)誤:期望%d,但是計(jì)算的值是%d", v, fib)
      }
   }
}

在這個(gè)單元測(cè)試中,我通過(guò) map 預(yù)定義了一組測(cè)試用例,然后通過(guò) Fibonacci 函數(shù)計(jì)算結(jié)果。同預(yù)定義的結(jié)果進(jìn)行比較,如果相等,則說(shuō)明 Fibonacci 函數(shù)計(jì)算正確,不相等則說(shuō)明計(jì)算錯(cuò)誤。

然后即可運(yùn)行如下命令,進(jìn)行單元測(cè)試:

? go test -v ./ch28

這行命令會(huì)運(yùn)行 ch28 目錄下的所有單元測(cè)試,因?yàn)槲抑粚?xiě)了一個(gè)單元測(cè)試,所以可以看到結(jié)果如下所示:

? go test -v ./ch28 
=== RUN   TestFibonacci
    main_test.go:21: 結(jié)果正確:n為0,值為0
    main_test.go:21: 結(jié)果正確:n為1,值為1
    main_test.go:21: 結(jié)果正確:n為6,值為8
    main_test.go:21: 結(jié)果正確:n為8,值為21
    main_test.go:21: 結(jié)果正確:n為9,值為34
    main_test.go:21: 結(jié)果正確:n為2,值為1
    main_test.go:21: 結(jié)果正確:n為3,值為2
    main_test.go:21: 結(jié)果正確:n為4,值為3
    main_test.go:21: 結(jié)果正確:n為5,值為5
    main_test.go:21: 結(jié)果正確:n為7,值為13
--- PASS: TestFibonacci (0.00s)
PASS
ok      gotour/ch28     (cached)

在打印的測(cè)試結(jié)果中,你可以看到 PASS 標(biāo)記,說(shuō)明單元測(cè)試通過(guò),而且還可以看到我在單元測(cè)試中寫(xiě)的日志。

這就是一個(gè)完整的 Go 語(yǔ)言單元測(cè)試用例,它是在 Go 語(yǔ)言提供的測(cè)試框架下完成的。Go 語(yǔ)言測(cè)試框架可以讓我們很容易地進(jìn)行單元測(cè)試,但是需要遵循五點(diǎn)規(guī)則。

  • 含有單元測(cè)試代碼的 go 文件必須以 _test.go 結(jié)尾,Go 語(yǔ)言測(cè)試工具只認(rèn)符合這個(gè)規(guī)則的文件。

  • 單元測(cè)試文件名 _test.go 前面的部分最好是被測(cè)試的函數(shù)所在的 go 文件的文件名,比如以上示例中單元測(cè)試文件叫 main_test.go,因?yàn)闇y(cè)試的 Fibonacci 函數(shù)在 main.go 文件里。

  • 單元測(cè)試的函數(shù)名必須以 Test 開(kāi)頭,是可導(dǎo)出的、公開(kāi)的函數(shù)。

  • 測(cè)試函數(shù)的簽名必須接收一個(gè)指向 testing.T 類(lèi)型的指針,并且不能返回任何值。

  • 函數(shù)名最好是 Test + 要測(cè)試的函數(shù)名,比如例子中是 TestFibonacci,表示測(cè)試的是 Fibonacci 這個(gè)函數(shù)。

遵循以上規(guī)則,你就可以很容易地編寫(xiě)單元測(cè)試了。單元測(cè)試的重點(diǎn)在于熟悉業(yè)務(wù)代碼的邏輯、場(chǎng)景等,以便盡可能地全面測(cè)試,保障代碼質(zhì)量。

Go語(yǔ)言怎么通過(guò)測(cè)試保證質(zhì)量

單元測(cè)試覆蓋率

以上示例中的 Fibonacci 函數(shù)是否被全面地測(cè)試了呢?這就需要用單元測(cè)試覆蓋率進(jìn)行檢測(cè)了。

Go 語(yǔ)言提供了非常方便的命令來(lái)查看單元測(cè)試覆蓋率。還是以 Fibonacci 函數(shù)的單元測(cè)試為例,通過(guò)一行命令即可查看它的單元測(cè)試覆蓋率。

? go test -v --coverprofile=ch28.cover ./ch28

這行命令包括 --coverprofile 這個(gè) Flag,它可以得到一個(gè)單元測(cè)試覆蓋率文件,運(yùn)行這行命令還可以同時(shí)看到測(cè)試覆蓋率。Fibonacci 函數(shù)的測(cè)試覆蓋率如下:

PASS
coverage: 85.7% of statements
ok      gotour/ch28     0.367s  coverage: 85.7% of statements

可以看到,測(cè)試覆蓋率為 85.7%。從這個(gè)數(shù)字來(lái)看,F(xiàn)ibonacci 函數(shù)應(yīng)該沒(méi)有被全面地測(cè)試,這時(shí)候就需要查看詳細(xì)的單元測(cè)試覆蓋率報(bào)告了。

運(yùn)行如下命令,可以得到一個(gè) HTML 格式的單元測(cè)試覆蓋率報(bào)告:

? go tool cover -html=ch28.cover -o=ch28.html

命令運(yùn)行后,會(huì)在當(dāng)前目錄下生成一個(gè) ch28.html 文件,使用瀏覽器打開(kāi)它,可以看到圖中的內(nèi)容:

Go語(yǔ)言怎么通過(guò)測(cè)試保證質(zhì)量

單元測(cè)試覆蓋率報(bào)告

紅色標(biāo)記的部分是沒(méi)有測(cè)試到的,綠色標(biāo)記的部分是已經(jīng)測(cè)試到的。這就是單元測(cè)試覆蓋率報(bào)告的好處,通過(guò)它你可以很容易地檢測(cè)自己寫(xiě)的單元測(cè)試是否完全覆蓋。

根據(jù)報(bào)告,我再修改一下單元測(cè)試,把沒(méi)有覆蓋的代碼邏輯覆蓋到,代碼如下:

fsMap[-1] = 0

也就是說(shuō),由于圖中 n<0 的部分顯示為紅色,表示沒(méi)有測(cè)試到,所以我們需要再添加一組測(cè)試用例,用于測(cè)試 n<0 的情況?,F(xiàn)在再運(yùn)行這個(gè)單元測(cè)試,查看它的單元測(cè)試覆蓋率,就會(huì)發(fā)現(xiàn)已經(jīng)是 100% 了。

基準(zhǔn)測(cè)試

除了需要保證我們編寫(xiě)的代碼的邏輯正確外,有時(shí)候還有性能要求。那么如何衡量代碼的性能呢?這就需要基準(zhǔn)測(cè)試了。

什么是基準(zhǔn)測(cè)試

基準(zhǔn)測(cè)試(Benchmark)是一項(xiàng)用于測(cè)量和評(píng)估軟件性能指標(biāo)的方法,主要用于評(píng)估你寫(xiě)的代碼的性能。

Go 語(yǔ)言的基準(zhǔn)測(cè)試

Go 語(yǔ)言的基準(zhǔn)測(cè)試和單元測(cè)試規(guī)則基本一樣,只是測(cè)試函數(shù)的命名規(guī)則不一樣?,F(xiàn)在還以 Fibonacci 函數(shù)為例,演示 Go 語(yǔ)言基準(zhǔn)測(cè)試的使用。

Fibonacci 函數(shù)的基準(zhǔn)測(cè)試代碼如下:

ch28/main_test.go

func BenchmarkFibonacci(b *testing.B){
   for i:=0;i<b.N;i++{
      Fibonacci(10)
   }
}

這是一個(gè)非常簡(jiǎn)單的 Go 語(yǔ)言基準(zhǔn)測(cè)試示例,它和單元測(cè)試的不同點(diǎn)如下:

  • 基準(zhǔn)測(cè)試函數(shù)必須以 Benchmark 開(kāi)頭,必須是可導(dǎo)出的;

  • 函數(shù)的簽名必須接收一個(gè)指向 testing.B 類(lèi)型的指針,并且不能返回任何值;

  • 最后的 for 循環(huán)很重要,被測(cè)試的代碼要放到循環(huán)里;

  • b.N 是基準(zhǔn)測(cè)試框架提供的,表示循環(huán)的次數(shù),因?yàn)樾枰磸?fù)調(diào)用測(cè)試的代碼,才可以評(píng)估性能。

寫(xiě)好了基準(zhǔn)測(cè)試,就可以通過(guò)如下命令來(lái)測(cè)試 Fibonacci 函數(shù)的性能:

? go test -bench=. ./ch28
goos: darwin
goarch: amd64
pkg: gotour/ch28
BenchmarkFibonacci-8     3461616               343 ns/op
PASS
ok      gotour/ch28     2.230s

運(yùn)行基準(zhǔn)測(cè)試也要使用 go test 命令,不過(guò)要加上 -bench 這個(gè) Flag,它接受一個(gè)表達(dá)式作為參數(shù),以匹配基準(zhǔn)測(cè)試的函數(shù),"."表示運(yùn)行所有基準(zhǔn)測(cè)試。

下面著重解釋輸出的結(jié)果??吹胶瘮?shù)后面的 -8 了嗎?這個(gè)表示運(yùn)行基準(zhǔn)測(cè)試時(shí)對(duì)應(yīng)的 GOMAXPROCS 的值。接著的 3461616 表示運(yùn)行 for 循環(huán)的次數(shù),也就是調(diào)用被測(cè)試代碼的次數(shù),最后的 343 ns/op 表示每次需要花費(fèi) 343 納秒。

基準(zhǔn)測(cè)試的時(shí)間默認(rèn)是 1 秒,也就是 1 秒調(diào)用 3461616 次、每次調(diào)用花費(fèi) 343 納秒。如果想讓測(cè)試運(yùn)行的時(shí)間更長(zhǎng),可以通過(guò) -benchtime 指定,比如 3 秒,代碼如下所示:

go test -bench=. -benchtime=3s ./ch28

計(jì)時(shí)方法

進(jìn)行基準(zhǔn)測(cè)試之前會(huì)做一些準(zhǔn)備,比如構(gòu)建測(cè)試數(shù)據(jù)等,這些準(zhǔn)備也需要消耗時(shí)間,所以需要把這部分時(shí)間排除在外。這就需要通過(guò) ResetTimer 方法重置計(jì)時(shí)器,示例代碼如下:

func BenchmarkFibonacci(b *testing.B) {
   n := 10
   b.ResetTimer() //重置計(jì)時(shí)器
   for i := 0; i < b.N; i++ {
      Fibonacci(n)
   }
}

這樣可以避免因?yàn)闇?zhǔn)備數(shù)據(jù)耗時(shí)造成的干擾。

除了 ResetTimer 方法外,還有 StartTimer 和 StopTimer 方法,幫你靈活地控制什么時(shí)候開(kāi)始計(jì)時(shí)、什么時(shí)候停止計(jì)時(shí)。

內(nèi)存統(tǒng)計(jì)

在基準(zhǔn)測(cè)試時(shí),還可以統(tǒng)計(jì)每次操作分配內(nèi)存的次數(shù),以及每次操作分配的字節(jié)數(shù),這兩個(gè)指標(biāo)可以作為優(yōu)化代碼的參考。要開(kāi)啟內(nèi)存統(tǒng)計(jì)也比較簡(jiǎn)單,代碼如下,即通過(guò) ReportAllocs() 方法:

func BenchmarkFibonacci(b *testing.B) {
   n := 10
   b.ReportAllocs() //開(kāi)啟內(nèi)存統(tǒng)計(jì)
   b.ResetTimer() //重置計(jì)時(shí)器
   for i := 0; i < b.N; i++ {
      Fibonacci(n)
   }
}

現(xiàn)在再運(yùn)行這個(gè)基準(zhǔn)測(cè)試,就可以看到如下結(jié)果:

? go test -bench=.  ./ch28
goos: darwin
goarch: amd64
pkg: gotour/ch28
BenchmarkFibonacci-8  2486265  486 ns/op  0 B/op  0 allocs/op
PASS
ok      gotour/ch28     2.533s

可以看到相比原來(lái)的基準(zhǔn)測(cè)試多了兩個(gè)指標(biāo),分別是 0 B/op 和 0 allocs/op。前者表示每次操作分配了多少字節(jié)的內(nèi)存,后者表示每次操作分配內(nèi)存的次數(shù)。這兩個(gè)指標(biāo)可以作為代碼優(yōu)化的參考,盡可能地越小越好。

小提示:以上兩個(gè)指標(biāo)是否越小越好?這是不一定的,因?yàn)橛袝r(shí)候代碼實(shí)現(xiàn)需要空間換時(shí)間,所以要根據(jù)自己的具體業(yè)務(wù)而定,做到在滿足業(yè)務(wù)的情況下越小越好。

并發(fā)基準(zhǔn)測(cè)試

除了普通的基準(zhǔn)測(cè)試外,Go 語(yǔ)言還支持并發(fā)基準(zhǔn)測(cè)試,你可以測(cè)試在多個(gè) goroutine 并發(fā)下代碼的性能。還是以 Fibonacci 為例,它的并發(fā)基準(zhǔn)測(cè)試代碼如下:

func BenchmarkFibonacciRunParallel(b *testing.B) {
   n := 10
   b.RunParallel(func(pb *testing.PB) {
      for pb.Next() {
         Fibonacci(n)
      }
   })
}

可以看到,Go 語(yǔ)言通過(guò) RunParallel 方法運(yùn)行并發(fā)基準(zhǔn)測(cè)試。RunParallel 方法會(huì)創(chuàng)建多個(gè) goroutine,并將 b.N 分配給這些 goroutine 執(zhí)行。

基準(zhǔn)測(cè)試實(shí)戰(zhàn)

相信你已經(jīng)理解了 Go 語(yǔ)言的基準(zhǔn)測(cè)試,也學(xué)會(huì)了如何使用,現(xiàn)在我以一個(gè)實(shí)戰(zhàn)幫你復(fù)習(xí)。

還是以 Fibonacci 函數(shù)為例,通過(guò)前面小節(jié)的基準(zhǔn)測(cè)試,會(huì)發(fā)現(xiàn)它并沒(méi)有分配新的內(nèi)存,也就是說(shuō) Fibonacci 函數(shù)慢并不是因?yàn)閮?nèi)存,排除掉這個(gè)原因,就可以歸結(jié)為所寫(xiě)的算法問(wèn)題了。

在遞歸運(yùn)算中,一定會(huì)有重復(fù)計(jì)算,這是影響遞歸的主要因素。解決重復(fù)計(jì)算可以使用緩存,把已經(jīng)計(jì)算好的結(jié)果保存起來(lái),就可以重復(fù)使用了。

基于這個(gè)思路,我將 Fibonacci 函數(shù)的代碼進(jìn)行如下修改:

//緩存已經(jīng)計(jì)算的結(jié)果
var cache = map[int]int{}
func Fibonacci(n int) int {
   if v, ok := cache[n]; ok {
      return v
   }
   result := 0
   switch {
   case n < 0:
      result = 0
   case n == 0:
      result = 0
   case n == 1:
      result = 1
   default:
      result = Fibonacci(n-1) + Fibonacci(n-2)
   }
   cache[n] = result
   return result
}

這組代碼的核心在于采用一個(gè) map 將已經(jīng)計(jì)算好的結(jié)果緩存、便于重新使用。改造后,我再來(lái)運(yùn)行基準(zhǔn)測(cè)試,看看剛剛優(yōu)化的效果,如下所示:

BenchmarkFibonacci-8  97823403  11.7 ns/op

可以看到,結(jié)果為 11.7 納秒,相比優(yōu)化前的 343 納秒,性能足足提高了 28 倍。

“Go語(yǔ)言怎么通過(guò)測(cè)試保證質(zhì)量”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI