首頁 > 軟體

Go單元測試工具gomonkey的使用

2022-06-23 14:00:58

Go 單元測試工具

測試分為4個層次

  • 單元測試:對程式碼進行測試
  • 整合測試:對一個服務的介面測試
  • 端到端測試(鏈路測試):從一個鏈路的入口輸入測試用例,驗證輸出的系統的結果
  • UI測試

常犯的錯誤:

  • 沒有斷言。沒有斷言的單測是沒有靈魂的。

單測的特徵:

  • A:(Automatic,自動化):單元測試應該是全自動執行的,並且非互動式的
  • I:(Independent,獨立性):為了保證單元測試穩定可靠且便於維護,單元測試用例之間決不能互相呼叫,也不能依賴執行的先後次序。
  • R:(Repeatable,可重複):單元測試通常會被放到持續整合中,每次有程式碼 check in 時單元測試都會被執行。

單測

程式碼 bug 總是在所難免, 越早發現問題解決成本越低, 單測可以儘早的暴露錯誤。提高程式碼之路,使得專案更高質量的交付。 起碼有三個優點:

  • 提高程式碼質量

編寫單測是自測的一部分,編寫新程式碼時增加相應的單測,可以幫助我們發現大部分的bug,有助於減少聯調時的調整,提高聯調效率。

  • 花更少的時間進行功能測試

功能測試成本相對較高,因為經常需要執行一系列操作以驗證結果是否符合預期。如果問題如果發現了問題,溝通和複測往往要花費很多的時間。

  • 花更少的時間進行迴歸測試

迴歸測試是為了避免在對應用程式進行更改時引入bug。測試人員不僅要測試他們的新特性,還要測試以前存在的特性,以驗證之前實現的特性是否仍然像預期的那樣執行。 通過單元測試,可以在每次構建之後,重新執行整個測試流程,以確保新程式碼不會破壞已有功能

  • 測試異常場景

一些異常的場景QA不好構造,比如並行出款是否資金安全,事務異常相關測試等等。而問題經常出現在這些異常的場景,可能引發線上問題甚至是事故。 而單元測試可通過mock的方式方便的模擬各種異常場景。

Go 單元測試工具

gomonkey

引入 gomonkey 有如下好處:

  • 隔離被測程式碼
  • 加速執行測試
  • 使執行變得確定
  • 模擬特殊情況

功能列表

  • 支援為一個函數打一個樁
  • 支援為一個函數打一個特定的樁序列
  • 支援為一個成員方法打一個樁
  • 支援為一個成員方法打一個特定的樁序列
  • 支援為一個函數變數打一個樁
  • 支援為一個函數變數打一個特定的樁序列
  • 支援為一個介面打樁
  • 支援為一個介面打一個特定的樁序列
  • 支援為一個全域性變數打一個樁

函數打樁, 對變數的 mock 實現原理跟 gostub 一樣都是通過 reflect 包實現的。除了 mock 變數,gomonkey 還可以直接 mock 匯出函數/方法、mock 程式碼所在包的非匯出函數

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 函數宣告是否相同,最後呼叫 ApplyGlobalVar
  • ApplyFunc(target, double interface{}):修改 target 的機器指令,跳轉到 double 執行
  • ApplyMethod(target reflect.Type, methodName string, double interface{}):修改 method 的機器指令,跳轉到 double 執行
  • ApplyFuncSeq(target interface{}, outputs []OutputCell):修改 target 的機器指令,跳轉到 gomonkey 生成的一個函數執行,每次呼叫會順序從 outputs 取出一個值返回
  • ApplyMethodSeq(target reflect.Type, methodName string, outputs []OutputCell):修改 target 的機器指令,跳轉到 gomonkey 生成的一個方法執行,每次呼叫會順序從 outputs 取出一個值返回
  • ApplyFuncVarSeq(target interface{}, outputs []OutputCell):gomonkey 生成一個函數順序返回 outputs 中的值,呼叫 ApplyGlobalVar

gomonkey 打樁失敗的可能原因

  • gomonkey 不是並行安全的。如果有多協程並行對同一個目標的打樁的情況,則需要將之前的協程先優雅退出。
  • 打樁目標為內聯的函數或成員方法。可通過命令列引數 -gcflags=-l (go1.10 版本之前)或-gcflags=all=-l(go1.10 版本及之後)關閉內聯優化。
  • gomonkey 對於私有成員方法的打樁失敗。go1.6 版本的反射機制支援私有成員方法的查詢,而 go1.7 及之後的版本卻不支援,所以當用戶使用 go1.7 及之後的版本時,gomonkey 對於私有成員方法的打樁會觸發異常。

goconvey

為全域性變數打一個樁

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

執行結果:

=== RUN   TestApplyGlobalVar
..
2 total assertions

--- PASS: TestApplyGlobalVar (0.00s)
PASS

為一個函數打樁

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(),返回了計算結果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)
	}
}

結果:

=== RUN   TestFunc
expected %v, got %v 2 3
    mock_func_test.go:91: expected 2, got 3
--- FAIL: TestFunc (0.00s)

FAIL

可以看到上面的結果,執行時失敗的,mock 沒有成功。

有時會遇到mock失效的情況,這個問題一般是內聯導致的。

什麼是內聯?

為了減少函數呼叫時的堆疊等開銷,對於簡短的函數,會在編譯時,直接內嵌呼叫的程式碼。

我們禁用下內聯,然後執行, go test -v -gcflags=-l mock_func_test.go

執行結果:

=== RUN   TestFunc
expected %v, got %v 2 2
--- PASS: TestFunc (0.00s)
PASS

對於 go 1.10以下版本,可使用-gcflags=-l禁用內聯,對於go 1.10及以上版本,可以使用-gcflags=all=-l。但目前使用下來,都可以。 關於gcflags的用法,可以使用 go tool compile --help 檢視 gcflags 各引數含義

到此這篇關於Go單元測試工具gomonkey的使用的文章就介紹到這了,更多相關Go gomonkey內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com