Golang 單元測試中打樁庫 gomonkey

一、打樁介紹

 樁或稱樁代碼,是指用來代替關聯代碼或者未實現代碼的代碼。如果函數 B 用 B1 來代替,那麼,B 稱爲原函數,B1 稱爲樁函數。打樁就是編寫或生成樁代碼。

1.1 目的

函數打樁的目的一般是隔離、佔位和控制。

  1.  隔離是指將測試任務從產品項目中分離出來,使之能夠獨立編譯、鏈接,並獨立運行。隔離的基本方法就是打樁,將測試任務之外的,並且與測試任務相關的代碼,用樁來代替,從而實現分離測試任務。例如函數 A 調用了函數 B,函數 B 又調用了函數 C 和 D,如果函數 B 用樁來代替,函數 A 就可以完全割斷與函數 C 和 D 的關係

  2. 補齊是指用樁來代替未實現的代碼,例如,函數 A 調用了函數 B,而函數 B 由其他程序員編寫,且未實現,那麼,可以用樁來代替函數 B,使函數 A 能夠運行並測試。補齊在並行開發中很常用。

  3. 控制是指在測試時,人爲設定相關代碼的行爲,使之符合測試需求。

Golang 裏打樁的庫也有很多,這邊只介紹 gomonkey。

二、gomonkey

gomonkey 是 golang 的一款打樁框架,目標是讓用戶在單元測試中低成本的完成打樁,從而將精力聚焦於業務功能的開發。gomonkey 接口友好,功能強大,目前已被很多項目使用,用戶遍及世界多個國家。

2.1 特性

  1. 支持爲一個函數打一個樁

  2. 支持爲一個成員方法打一個樁

  3. 支持爲一個全局變量打一個樁

  4. 支持爲一個函數變量打一個樁

  5. 支持爲一個函數打一個特定的樁序列

  6. 支持爲一個成員方法打一個特定的樁序列

  7. 支持爲一個函數變量打一個特定的樁序列

2.2 方法說明

90tiV5

2.3 工作原理

gomonkey 是爲函數、變量打樁,但是對於函數以及方法的模擬替換,在 Go 這種靜態強類型語言中不太容易,因爲我們的代碼邏輯已經是聲明好的,因此,我們很難通過編碼的方式將其替換掉。

所以,gomonkey 提供了讓我們在運行時替換原函數 / 方法的能力。雖然說我們在語言層面很難去替換運行中的函數體,但是代碼最終都會轉換成機器可以理解的彙編指令,因此,我們可以通過創建彙編指令來改寫函數。

在 gomonkey 打樁的過程中,其核心函數其實是 ApplyCore。不管是對函數打樁還是對方法打樁,實際上最後都會調用這個 ApplyCore 函數,如下:

ApplyCore 函數的具體實現如下:

func (this *Patches) ApplyCore(target, double reflect.Value) *Patches {
    this.check(target, double)
    if _, ok := this.originals[target]; ok {
        panic("patch has been existed")
    }
    this.valueHolders[double] = double
    original := replace(*(*uintptr)(getPointer(target)), uintptr(getPointer(double)))
    this.originals[target] = original
    return this
}

可以看到,獲取到傳入的原始函數和替換函數做了一個 replace 的操作,這裏就是替換的邏輯所在了。replace 函數原型如下:

func replace(target, double uintptr) []byte {
    code := buildJmpDirective(double)
    bytes := entryAddress(target, len(code))
    original := make([]byte, len(bytes))
    copy(original, bytes)
    modifyBinary(target, code)
    return original
}

buildJmpDirective 構建了一個函數跳轉的指令,把目標函數指針移動到寄存器 rdx 中,然後跳轉到寄存器 rdx 中函數指針指向的地址。之後通過 modifyBinary 函數,先通過 entryAddress 方法獲取到原函數所在的內存地址,之後通過 syscall.Mprotect 方法打開內存保護,將函數跳轉指令以 bytes 數組的形式調用 copy 方法寫入到原函數所在內存之中,最終達到替換的目的。此外,這裏 replace 方法還保留了原函數的副本,方便後續函數 mock 的恢復。

2.4 限制

gomonkey 作爲一個打樁的工具,使用場景還是比較廣泛,可以使用我們大部分的應用場景。但是,它依然還是有很多限制,它必須要找到該方法對應的真實的類(結構體):

  1. 內聯優化一般用於能夠快速執行的函數,因爲在這種情況下函數調用的時間消耗顯得更爲突出,同時內聯體量小的函數也不會明顯增加編譯後的執行文件佔用的空間。Go 中,函數體內包含:閉包調用,select ,for ,defer,go 關鍵字的的函數不會進行內聯。並且除了這些,還有其它的限制。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/f8R87wZ2_dInyFQE9379Lw