Go單元測試工具gomonkey的使用
Go 單元測試工具
測試分為4個(gè)層次
- 單元測試:對代碼進(jìn)行測試
- 集成測試:對一個(gè)服務(wù)的接口測試
- 端到端測試(鏈路測試):從一個(gè)鏈路的入口輸入測試用例,驗(yàn)證輸出的系統(tǒng)的結(jié)果
- UI測試

常犯的錯(cuò)誤:
- 沒有斷言。沒有斷言的單測是沒有靈魂的。
單測的特征:
- A:(Automatic,自動(dòng)化):單元測試應(yīng)該是全自動(dòng)執(zhí)行的,并且非交互式的
- I:(Independent,獨(dú)立性):為了保證單元測試穩(wěn)定可靠且便于維護(hù),單元測試用例之間決不能互相調(diào)用,也不能依賴執(zhí)行的先后次序。
- R:(Repeatable,可重復(fù)):單元測試通常會被放到持續(xù)集成中,每次有代碼 check in 時(shí)單元測試都會被執(zhí)行。
單測
代碼 bug 總是在所難免, 越早發(fā)現(xiàn)問題解決成本越低, 單測可以盡早的暴露錯(cuò)誤。提高代碼之路,使得項(xiàng)目更高質(zhì)量的交付。 起碼有三個(gè)優(yōu)點(diǎn):
- 提高代碼質(zhì)量
編寫單測是自測的一部分,編寫新代碼時(shí)增加相應(yīng)的單測,可以幫助我們發(fā)現(xiàn)大部分的bug,有助于減少聯(lián)調(diào)時(shí)的調(diào)整,提高聯(lián)調(diào)效率。
- 花更少的時(shí)間進(jìn)行功能測試
功能測試成本相對較高,因?yàn)榻?jīng)常需要執(zhí)行一系列操作以驗(yàn)證結(jié)果是否符合預(yù)期。如果問題如果發(fā)現(xiàn)了問題,溝通和復(fù)測往往要花費(fèi)很多的時(shí)間。
- 花更少的時(shí)間進(jìn)行回歸測試
回歸測試是為了避免在對應(yīng)用程序進(jìn)行更改時(shí)引入bug。測試人員不僅要測試他們的新特性,還要測試以前存在的特性,以驗(yàn)證之前實(shí)現(xiàn)的特性是否仍然像預(yù)期的那樣運(yùn)行。 通過單元測試,可以在每次構(gòu)建之后,重新運(yùn)行整個(gè)測試流程,以確保新代碼不會破壞已有功能
- 測試異常場景
一些異常的場景QA不好構(gòu)造,比如并發(fā)出款是否資金安全,事務(wù)異常相關(guān)測試等等。而問題經(jīng)常出現(xiàn)在這些異常的場景,可能引發(fā)線上問題甚至是事故。 而單元測試可通過mock的方式方便的模擬各種異常場景。
Go 單元測試工具
gomonkey
引入 gomonkey 有如下好處:
- 隔離被測代碼
- 加速執(zhí)行測試
- 使執(zhí)行變得確定
- 模擬特殊情況
功能列表
- 支持為一個(gè)函數(shù)打一個(gè)樁
- 支持為一個(gè)函數(shù)打一個(gè)特定的樁序列
- 支持為一個(gè)成員方法打一個(gè)樁
- 支持為一個(gè)成員方法打一個(gè)特定的樁序列
- 支持為一個(gè)函數(shù)變量打一個(gè)樁
- 支持為一個(gè)函數(shù)變量打一個(gè)特定的樁序列
- 支持為一個(gè)接口打樁
- 支持為一個(gè)接口打一個(gè)特定的樁序列
- 支持為一個(gè)全局變量打一個(gè)樁
函數(shù)打樁, 對變量的 mock 實(shí)現(xiàn)原理跟 gostub 一樣都是通過 reflect 包實(shí)現(xiàn)的。除了 mock 變量,gomonkey 還可以直接 mock 導(dǎo)出函數(shù)/方法、mock 代碼所在包的非導(dǎo)出函數(shù)
Go monkey Permission Denied 解決方案:https://github.com/eisenxp/macos-golink-wrapper
mv $GOROOT/pkg/tool/darwin_amd64/link $GOROOT/pkg/tool/darwin_amd64/original_link cp https://github.com/eisenxp/macos-golink-wrapper/link $GOROOT/pkg/tool/darwin_amd64/link
下載文件,然后再 cp
wget https://raw.githubusercontent.com/eisenxp/macos-golink-wrapper/main/link
gomonkey 提供了如下 mock 方法:
- ApplyGlobalVar(target, double interface{}):使用 reflect 包,將 target 的值修改為 double
- ApplyFuncVar(target, double interface{}):檢查 target 是否為指針類型,與 double 函數(shù)聲明是否相同,最后調(diào)用 ApplyGlobalVar
- ApplyFunc(target, double interface{}):修改 target 的機(jī)器指令,跳轉(zhuǎn)到 double 執(zhí)行
- ApplyMethod(target reflect.Type, methodName string, double interface{}):修改 method 的機(jī)器指令,跳轉(zhuǎn)到 double 執(zhí)行
- ApplyFuncSeq(target interface{}, outputs []OutputCell):修改 target 的機(jī)器指令,跳轉(zhuǎn)到 gomonkey 生成的一個(gè)函數(shù)執(zhí)行,每次調(diào)用會順序從 outputs 取出一個(gè)值返回
- ApplyMethodSeq(target reflect.Type, methodName string, outputs []OutputCell):修改 target 的機(jī)器指令,跳轉(zhuǎn)到 gomonkey 生成的一個(gè)方法執(zhí)行,每次調(diào)用會順序從 outputs 取出一個(gè)值返回
- ApplyFuncVarSeq(target interface{}, outputs []OutputCell):gomonkey 生成一個(gè)函數(shù)順序返回 outputs 中的值,調(diào)用 ApplyGlobalVar
gomonkey 打樁失敗的可能原因
- gomonkey 不是并發(fā)安全的。如果有多協(xié)程并發(fā)對同一個(gè)目標(biāo)的打樁的情況,則需要將之前的協(xié)程先優(yōu)雅退出。
- 打樁目標(biāo)為內(nèi)聯(lián)的函數(shù)或成員方法??赏ㄟ^命令行參數(shù) -gcflags=-l (go1.10 版本之前)或-gcflags=all=-l(go1.10 版本及之后)關(guān)閉內(nèi)聯(lián)優(yōu)化。
- gomonkey 對于私有成員方法的打樁失敗。go1.6 版本的反射機(jī)制支持私有成員方法的查詢,而 go1.7 及之后的版本卻不支持,所以當(dāng)用戶使用 go1.7 及之后的版本時(shí),gomonkey 對于私有成員方法的打樁會觸發(fā)異常。
goconvey
為全局變量打一個(gè)樁
package unittest
import (
"testing"
"github.com/agiledragon/gomonkey"
"github.com/smartystreets/goconvey/convey"
)
var num = 10 //全局變量
func TestApplyGlobalVar(t *testing.T) {
convey.Convey("TestApplyGlobalVar", t, func() {
convey.Convey("change", func() {
patches := gomonkey.ApplyGlobalVar(&num, 150)
defer patches.Reset()
convey.So(num, convey.ShouldEqual, 150)
})
convey.Convey("recover", func() {
convey.So(num, convey.ShouldEqual, 10)
})
})
}
執(zhí)行結(jié)果:
=== RUN TestApplyGlobalVar
..
2 total assertions--- PASS: TestApplyGlobalVar (0.00s)
PASS
為一個(gè)函數(shù)打樁
func networkCompute(a, b int) (int, error) {
// do something in remote computer
c := a + b
return c, nil
}
func Compute(a, b int) (int, error) {
sum, err := networkCompute(a, b)
return sum, err
}
func TestFunc(t *testing.T) {
// mock 了 networkCompute(),返回了計(jì)算結(jié)果2
patches := gomonkey.ApplyFunc(networkCompute, func(a, b int) (int, error) {
return 2, nil
})
defer patches.Reset()
sum, err := Compute(1, 2)
println("expected %v, got %v", 2, sum)
if sum != 2 || err != nil {
t.Errorf("expected %v, got %v", 2, sum)
}
}
結(jié)果:
=== RUN TestFunc
expected %v, got %v 2 3
mock_func_test.go:91: expected 2, got 3
--- FAIL: TestFunc (0.00s)FAIL
可以看到上面的結(jié)果,執(zhí)行時(shí)失敗的,mock 沒有成功。
有時(shí)會遇到mock失效的情況,這個(gè)問題一般是內(nèi)聯(lián)導(dǎo)致的。
什么是內(nèi)聯(lián)?
為了減少函數(shù)調(diào)用時(shí)的堆棧等開銷,對于簡短的函數(shù),會在編譯時(shí),直接內(nèi)嵌調(diào)用的代碼。
我們禁用下內(nèi)聯(lián),然后執(zhí)行, go test -v -gcflags=-l mock_func_test.go
執(zhí)行結(jié)果:
=== RUN TestFunc
expected %v, got %v 2 2
--- PASS: TestFunc (0.00s)
PASS
對于 go 1.10以下版本,可使用-gcflags=-l禁用內(nèi)聯(lián),對于go 1.10及以上版本,可以使用-gcflags=all=-l。但目前使用下來,都可以。 關(guān)于gcflags的用法,可以使用 go tool compile --help 查看 gcflags 各參數(shù)含義
到此這篇關(guān)于Go單元測試工具gomonkey的使用的文章就介紹到這了,更多相關(guān)Go gomonkey內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Golang連接池的幾種實(shí)現(xiàn)案例小結(jié)
這篇文章主要介紹了Golang連接池的幾種實(shí)現(xiàn)案例小結(jié),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03
Go pprof內(nèi)存指標(biāo)含義備忘錄及案例分析
這篇文章主要介紹了Go pprof內(nèi)存指標(biāo)含義備忘錄問題,小編特此把問題及案例分享到腳本之家平臺供大家學(xué)習(xí),需要的朋友可以參考下2020-03-03

