Go?語言進階單元測試示例詳解
前言
本文從單元測試實踐角度出發(fā),提升對代碼質(zhì)量的意識。
本文內(nèi)容主要包括:單元測試、Mock測試、基準測試。
測試
測試可以提高代碼的質(zhì)量、減少事故的發(fā)生。
測試又分為:回歸測試、集成測試、單元測試。
回歸測試是指對QA手動回歸一些特定場景,可以理解為我們說的手動點點。
集成測試是指對系統(tǒng)功能維度做驗證,比如對服務(wù)暴露的接口驗證,一般是自動化的驗證。
單元測試是指在開發(fā)階段,開發(fā)者對單獨的函數(shù)、模塊做驗證,寫一些測試用例。
單元測試
單元測試組成部分:輸入、輸出、測試單元、與期望的校對,測試單元又包括函數(shù)、接口、模塊、復(fù)雜的聚合函數(shù)等。
通過單元測試的輸出再與期望輸出進行校對,來驗證代碼的正確性。通過單元測試可以保證代碼的質(zhì)量,也可以在一定程度上提升效率,比如通過運行單元測試可以快速定位到有問題的代碼。
規(guī)則
單元測試的編寫有一定的規(guī)則:
- 所有測試文件以
_test.go
結(jié)尾 - 測試方法名以
Test
開頭,參數(shù)要用testing
func TestXxx(t *testing.T)
- 測試初始化邏輯放到
TestMain
中 - 通過
go test
命令進行測試
示例
import "testing" func TestHelloTom(t *testing.T) { output := HelloTom() expectOutPut := "Tom" if output != expectOutPut { t.Errorf("Expected %s do not match actual %s", expectOutPut, output) } } func HelloTom() string { return "Jerry" }
通過go test
命令運行得到以下結(jié)果,從測試結(jié)果里可以看出,我們期望得到的是Tom
,但實際得到的卻是Jerry
。
--- FAIL: TestHelloTom (0.00s) helloTom_test.go:9: Expected Tom do not match actual Jerry FAIL exit status 1 FAIL learning/mytesting 0.496s
assert
另外我們可以使用開源的assert
包,來代替我們自己的if
判斷。
func TestHelloTom(t *testing.T) { output := HelloTom() expectOutPut := "Tom" assert.Equal(t, expectOutPut, output) }
再次通過go test
命令運行得到以下結(jié)果,輸出了更詳細的堆棧信息。
覆蓋率
如何評估單元測試呢?是通過代碼覆蓋率來評估的,評估的標(biāo)準包括:
- 衡量代碼是否經(jīng)過了足夠的測試
- 評價項目的測試水準
- 評估項目是否達到了高水平的測試等級
下面通過一個例子來看下代碼覆蓋率:
// judgepass.go func JudgePassLine(score int16) bool { if score >= 16 { return true } return false } // judgepass_test.go func TestJudgePassLineTrue(t *testing.T) { isPass := JudgePassLine(70) assert.Equal(t, true, isPass) }
通過命令來看覆蓋率go test judgepass_test.go judgepass.go --cover
輸出結(jié)果為:
ok command-line-arguments 0.327s coverage: 66.7% of statements
可以看到,提示出的代碼覆蓋率為66.7%
,因為只走了一個if
分支。如果要想達到100%
的代碼覆蓋率的話,就要把所有的分支都要覆蓋到。
一般的覆蓋率為50%~60%
,較高的覆蓋率要達到80%+
。要注意:測試分支相互獨立、要全面覆蓋,測試單元粒度要足夠小,滿足函數(shù)單一職責(zé)。
依賴
一個實際項目不可能只是一個簡單的單體函數(shù),肯定會很復(fù)雜,存在其他的依賴,比如依賴數(shù)據(jù)庫、redis、文件等外部依賴。
單元測試一般有兩個目標(biāo):冪等、穩(wěn)定。
冪等:重復(fù)執(zhí)行一個用例、調(diào)用一個接口,返回的結(jié)果是一樣的。
穩(wěn)定:單元測試是相互隔離的,在任何時間都能獨立運行。
Mock
如果單元測試用到數(shù)據(jù)庫、redis等,在單元測試里直接連接會涉及到網(wǎng)絡(luò)傳輸,這是不穩(wěn)定的,所以要用到Mock
機制。
開源Mock
框架:github.com/bouk/monkey
這個Mock
包可以對函數(shù)或方法進行打樁,打樁就是用一個函數(shù)A來替換一個函數(shù)B。
monkey
的實現(xiàn)原理主要是在運行時,通過Go
的unsafe
包能夠?qū)?nèi)存中函數(shù)的地址替換為運行時函數(shù)的地址,最終調(diào)用的是打樁函數(shù),從而實現(xiàn)Mock
的功能。
Mock
常用方法:Patch
、Unpatch
。
Patch
方法有兩個參數(shù),target
為替換的函數(shù)(原函數(shù)),replacement
為要替換成的函數(shù)。
func Patch(target, replacement interface{}) *PatchGuard { t := reflect.ValueOf(target) r := reflect.ValueOf(replacement) patchValue(t, r) return &PatchGuard{t, r} }
Unpatch
為測試結(jié)束之后,要把打的樁給卸載掉。
func Unpatch(target interface{}) bool { return unpatchValue(reflect.ValueOf(target)) }
下面通過Mock
來模擬對文件的操作。
func TestProcessFirstLineWithMock(t *testing.T) { monkey.Patch(ReadFirstLine, func() string { return "line110" }) defer monkey.Unpatch(ReadFirstLine) line := ProcessFirstLine() assert.Equal(t, "line000", line) } func ReadFirstLine() string { open, err := os.Open("log") defer open.Close() if err != nil { return "" } scanner := bufio.NewScanner(open) for scanner.Scan() { return scanner.Text() } return "" } func ProcessFirstLine() string { line := ReadFirstLine() destLine := strings.ReplaceAll(line, "11", "00") return destLine }
該測試用例對ProcessFirstLine
函數(shù)進行測試,這個函數(shù)調(diào)用了ReadFirstLine
函數(shù),涉及到文件的操作,通過Mock
對文件的操作進行打樁,這樣就避免了其他進程對文件操作的影響。
基準測試
Go
還提供了基準測試框架,可以測試一段程序的性能、CPU消耗,可以對代碼做性能分析,測試方法與單元測試類似。
基準測試規(guī)則:
- 基準測試以Benchmark為前綴
- 需要一個*testing.B類型的參數(shù)b
- 基準測試必須要執(zhí)行b.N次
下面通過一個模擬負載均衡的例子,來看下基準測試:
var ServerIndex [10]int func InitServerIndex() { for i := 0; i < 10; i++ { ServerIndex[i] = i + 100 } } func Select() int { return ServerIndex[rand.Intn(10)] } func BenchmarkSelect(b *testing.B) { InitServerIndex() b.ResetTimer() for i := 0; i < b.N; i++ { Select() } } func BenchmarkSelectParallel(b *testing.B) { InitServerIndex() b.ResetTimer() b.RunParallel(func (pb *testing.PB) { for pb.Next() { Select() } }) }
通過命令 go test -bench=.
運行測試,輸出結(jié)果如下:
goos: darwin
goarch: amd64
pkg: learning/bench
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.70GHz
BenchmarkSelect-8 50264580 23.47 ns/op
BenchmarkSelectParallel-8 13717840 133.4 ns/op
PASS
ok learning/bench 4.559s
BenchmarkSelect-8
表示對Select
函數(shù)進行基準測試,數(shù)字8
表示 GOMAXPROCS
的值。
23.47 ns/op
表示每次調(diào)用Select
函數(shù)耗時23.47ns
。
50264580
這是50264580
次調(diào)用的平均值。
字節(jié)開源的go框架:github.com/bytedance/g…
引用 Go 語言進階與依賴管理
以上就是Go 語言進階單元測試示例詳解的詳細內(nèi)容,更多關(guān)于Go 語言單元測試的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Go語言關(guān)于幾種深度拷貝(deepcopy)方法的性能對比
這篇文章主要介紹了Go語言關(guān)于幾種深度拷貝(deepcopy)方法的性能對比,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01基于Go語言實現(xiàn)的簡易api網(wǎng)關(guān)的示例代碼
本文主要介紹了基于Go語言實現(xiàn)的簡易api網(wǎng)關(guān),文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-12-12用golang實現(xiàn)一個定時器任務(wù)隊列實例
golang中提供了2種定時器timer和ticker,分別是一次性定時器和重復(fù)任務(wù)定時器。這篇文章主要介紹了用golang實現(xiàn)一個定時器任務(wù)隊列實例,非常具有實用價值,需要的朋友可以參考下2018-05-05