mock go 程式的新方法

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

一直以來,我都認為在 go 裡面 mock 是非常困難的。不像動態語言或者跑在 VM 上的語言,go 要求在開發的時候就給 mock 介入預留空間,不然測試的時候會不得其門而入。開發的時候需要頭疼的事情可多了,還要求再考慮下可測試性,真有點強人所難。另外第三方庫並不一定給 mock 預留空間,遇到這種情況只能乾瞪眼繞路走。很多時候,無法 mock 掉某些帶副作用的函數,就不能覆蓋掉目標路徑。既然測試不到關鍵的路徑,那乾脆就不寫測試了。結果是,項目裡很多 go 代碼事實上一直都沒有被測試覆蓋掉。

但最近我發現了一個庫:https://github.com/bouk/monkey
似乎可以跟開頭的煩惱永別了?小範圍地體驗了下,感覺還是挺好用的。

長話短說,monkey 庫通過修改記憶體位址的方式,替換目標函數的實際執行地址,實現(幾乎)任意函數的 mock。你可以指定目標函數,然後定義一個匿名函數替換掉它。替換的記錄會存在一個全域表裡,不需要的時候可以通過它重新恢複原來的目標函數。由於採用的是修改記憶體位址的黑科技,作者建議千萬不要用在測試環境以外的地方。目前僅支援x86架構上的 Linux 和 Mac,Windows 似乎沒有測試過?不管怎樣,支援 Linux 和 Mac 就足以覆蓋開發機和 CI 環境了。

monkey 庫用起來非常簡單,直接邊上範例程式碼,邊解釋好了:

package mainimport (    "fmt"    "github.com/bouk/monkey"    "os"    "os/exec"    "reflect"    "testing")// 假如我們要測試函數 callfunc call(cmd string) (int, string) {    bytes, err := exec.Command("sh", "-c", cmd).CombinedOutput()    output := string(bytes)    if err != nil {        return 1, reportExecFailed(output)    }    return 0, output}// 上面的函數會調用它,這個函數一定要mock掉!func reportExecFailed(msg string) string {    os.Exit(1) // 討人嫌的副作用    return msg}func TestExecSussess(t *testing.T) {    // 恢複 patch 修改    // 實際使用中會把 UnpatchAll 放到 teardown 函數裡    // 不過在 go 內建的 testing 裡就這麼處理了    defer monkey.UnpatchAll()    // mock 掉 exec.Command 返回的 *exec.Cmd 的 CombinedOutput 方法    monkey.PatchInstanceMethod(        reflect.TypeOf((*exec.Cmd)(nil)),        "CombinedOutput", func(_ *exec.Cmd) ([]byte, error) {            return []byte("results"), nil        },    )    // mock 掉 reportExecFailed 函數    monkey.Patch(reportExecFailed, func(msg string) string {        return msg    })    rc, output := call("any")    if rc != 0 {        t.Fail()    }    if output != "results" {        t.Fail()    }}func TestExecFailed(t *testing.T) {    defer monkey.UnpatchAll()    // 上次 mock 的是執行成功的情況,這一次輪到執行失敗    monkey.PatchInstanceMethod(        reflect.TypeOf((*exec.Cmd)(nil)),        "CombinedOutput", func(_ *exec.Cmd) ([]byte, error) {            return []byte(""), fmt.Errorf("sth bad happened")        },    )    monkey.Patch(reportExecFailed, func(msg string) string {        return msg    })    rc, output := call("any")    if rc != 1 {        t.Fail()    }    if output != "" {        t.Fail()    }}

執行 go test xx_test.go,可以運行上面的代碼。

測試中常有的一個需求:在位置 A 需要 mock 掉函數,在位置 B 裡需要調用原來的函數才能運行下去。這時候需要使用 monkey 庫提供的 PatchGuard 結構體。官方文檔中有個樣本,這裡稍微調整下:

package mainimport (    "fmt"    "github.com/bouk/monkey"    "strings")func main() {    var guard *monkey.PatchGuard    guard = monkey.Patch(fmt.Println, func(a ...interface{}) (n int, err error) {        s := make([]interface{}, len(a))        for i, v := range a {            s[i] = strings.Replace(fmt.Sprint(v), "hell", "*bleep*", -1)        }        // 以下代碼等價於        // guard.Unpatch()        // defer guard.Restore()        // return fmt.Println(s...)        guard.Unpatch()        n, err = fmt.Println(s...)        guard.Restore()        return    })    fmt.Println("what the hell?") // what the *bleep*?    fmt.Println("what the hell?") // what the *bleep*?}

上面的代碼關鍵在於,調用原來的函數之前先調用一次 Unpatch,恢複到 mock 之前的情況;然後在調用了原函數之後,調用一次 Restore,重新打上 mock。剩下的,無非是根據輸入參數來判斷現在是運行到位置 A,還是位置 B。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.