Go 單測從零到溜系列—4- 使用 monkey 打樁

這是 Go 語言單元測試從零到溜系列教程的第 4 篇,介紹瞭如何在單元測試中使用 monkey 進行打樁。

在上一篇《Go 單測從零到溜系列 3—mock 接口測試》中,我們介紹瞭如何在單元測試中使用gomockgostub工具 mock 接口及打樁。

在這一篇中我們將介紹一個更強大的打樁工具——monkey,它支持爲任意函數及方法進行打樁。

monkey

介紹

monkey 是一個 Go 單元測試中十分常用的打樁工具,它在運行時通過彙編語言重寫可執行文件,將目標函數或方法的實現跳轉到樁實現,其原理類似於熱補丁。

monkey 庫很強大,但是使用時需注意以下事項:

安裝

go get bou.ke/monkey

使用示例

假設你們公司中臺提供了一個用戶中心的庫varys,使用這個庫可以很方便的根據 uid 獲取用戶相關信息。但是當你編寫代碼的時候這個庫還沒實現,或者這個庫要經過內網請求但你現在沒這能力,這個時候要爲MyFunc編寫單元測試,就需要做一些 mock 工作。

// func.go

func MyFunc(uid int64)string{
 u, err := varys.GetInfoByUID(uid)
 if err != nil {
  return "welcome"
 }

 // 這裏是一些邏輯代碼...

 return fmt.Sprintf("hello %s\n", u.Name)
}

我們使用monkey庫對varys.GetInfoByUID進行打樁。

// func_test.go

func TestMyFunc(t *testing.T) {
 // 對 varys.GetInfoByUID 進行打樁
 // 無論傳入的uid是多少,都返回 &varys.UserInfo{Name: "liwenzhou"}, nil
 monkey.Patch(varys.GetInfoByUID, func(int64)(*varys.UserInfo, error) {
  return &varys.UserInfo{Name: "liwenzhou"}, nil
 })

 ret := MyFunc(123)
 if !strings.Contains(ret, "liwenzhou"){
  t.Fatal()
 }
}

執行單元測試:

注意:這裏爲防止內聯優化添加了-gcflags=-l參數。

go test -run=TestMyFunc -v -gcflags=-l

輸出:

=== RUN   TestMyFunc
--- PASS: TestMyFunc (0.00s)
PASS
ok      monkey_demo     0.009s

除了對函數進行 mock 外monkey也支持對方法進行 mock。

// method.go

type User struct {
 Name string
 Birthday string
}

// CalcAge 計算用戶年齡
func (u *User) CalcAge() int {
 t, err := time.Parse("2006-01-02", u.Birthday)
 if err != nil {
  return -1
 }
 return int(time.Now().Sub(t).Hours()/24.0)/365
}


// GetInfo 獲取用戶相關信息
func (u *User) GetInfo()string{
 age := u.CalcAge()
 if age <= 0 {
  return fmt.Sprintf("%s很神祕,我們還不瞭解ta。", u.Name)
 }
 return fmt.Sprintf("%s今年%d歲了,ta是我們的朋友。", u.Name, age)
}

如果我們爲GetInfo編寫單元測試的時候CalcAge方法的功能還未完成,這個時候我們可以使用 monkey 進行打樁。

// method_test.go

func TestUser_GetInfo(t *testing.T) {
 var u = &User{
  Name:     "q1mi",
  Birthday: "1990-12-20",
 }

 // 爲對象方法打樁
 monkey.PatchInstanceMethod(reflect.TypeOf(u)"CalcAge", func(*User)int {
  return 18
 })

 ret := u.GetInfo()  // 內部調用u.CalcAge方法時會返回18
 if !strings.Contains(ret, "朋友"){
  t.Fatal()
 }
}

執行單元測試:

❯ go test -run=User -v
=== RUN   TestUser_GetInfo
--- PASS: TestUser_GetInfo (0.00s)
PASS
ok      monkey_demo     0.012s

monkey基本上能滿足我們在單元測試中打樁的任何需求。

社區中還有一個參考 monkey 庫實現的 gomonkey 庫,原理和使用過程基本相似,這裏就不再囉嗦了。除此之外社區裏還有一些其他打樁工具如 GoStub(上一篇介紹過爲全局變量打樁)等。

熟練使用各種打樁工具能夠讓我們更快速地編寫合格的單元測試,爲我們的軟件保駕護航。

總結

本文通過外部函數依賴及內部方法依賴兩個示例,介紹瞭如何使用monkey對依賴的函數和方法進行打樁。

在下一篇中,我們將介紹編寫單元測試時常用的工具——goconvey

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/DNyHG5xIXefJXhpVwB3R1A