天天看點

go 單元測試 gomonkey1.安裝2.使用方法3.參考

目錄

  • 1.安裝
  • 2.使用方法
    • 2.1 mock一個函數
    • 2.2 mock 一個方法
    • 2.3 mock 一個全局變量
    • 2.4 mock 一個函數序列
  • 3.參考

單元測試中,經常需要mock。

例如,一個函數中,需要調用網絡連接配接函數建立連接配接。做單元測試時,這個建立連接配接的函數就可以mock一下,而不真正去嘗試建立連接配接。

mock 有時也稱為“打樁”。

例如,mock一個函數,可以說,為一個函數打樁。

在golang中,

gomonkey 就是這樣的工具庫。

本文主要介紹使用gomonkey進行mock。

1.安裝

$ go get github.com/agiledragon/gomonkey
           

2.使用方法

2.1 mock一個函數

下面例子中,調用鍊是:Compute()–> networkCompute()。本地單測時,一般不會建立網絡連接配接,是以需要mock netWorkCompute()。

//compute_test.go

package main
import (
    "testing"
    
    "github.com/agiledragon/gomonkey"
)


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 TestCompute(t *testing.T) {
    patches := gomonkey.ApplyFunc(networkCompute, func(a, b int) (int,error){
    return 2, nil
    })
    
    defer patches.Reset()

    sum, err := Compute(1, 1)
    if sum != 2 || err != nil {
        t.Errorf("expected %v, got %v", 2, sum)
    }
    
}
           

output:

go test -v compute_test.go
=== RUN   TestCompute
--- PASS: TestCompute (0.00s)
PASS
ok  	command-line-arguments	0.006s
           

上面代碼中,我們mock 了 networkCompute(),傳回了計算結果2。

再例如:

下面代碼中,調用鍊:Convert2Json() --> json.Marshal()

嘗試mock json.Marshal()。

// json_test.go
package j
import (
    "testing"
    "encoding/json"
    
    "github.com/agiledragon/gomonkey"
)

type Host struct {
    IP string
    Name string
}

func Convert2Json(h *Host) (string, error){
    b, err := json.Marshal(h)
    return string(b), err
}

func TestConvert2Json(t *testing.T) {
    patches := gomonkey.ApplyFunc(json.Marshal, func(v interface{}) ([]byte,error){
    return []byte(`{"IP":"192.168.23.92","Name":"Sky"}`), nil
    })
    
    defer patches.Reset()


    h := Host{Name: "Sky", IP: "192.168.23.92"}
    s, err := Convert2Json(&h)

    expectedString := `{"IP":"192.168.23.92","Name":"Sky"}`
    
    if s != expectedString || err != nil {
        t.Errorf("expected %v, got %v", expectedString, s)
    }
    
}

           

output:

go test -v json_test.go
=== RUN   TestConvert2Json
--- PASS: TestConvert2Json (0.00s)
PASS
ok  	command-line-arguments	0.006s
           

2.2 mock 一個方法

例子中,定義一個類型,類型中有兩個方法Compute(), NetworkCompute(),調用關系為:Compute()–>NetworkCompute()。

//compute_test.go

package c
import (
    "reflect"
    "testing"
    
    "github.com/agiledragon/gomonkey"
)

type Computer struct {
    
}

func(t *Computer) NetworkCompute(a, b int) (int, error){
    // do something in remote computer
    c := a + b
    
    return c, nil
}

func(t *Computer) Compute(a, b int)(int, error) {
    sum, err := t.NetworkCompute(a, b)
    return sum, err
}

func TestCompute(t *testing.T) {
    var c *Computer
    patches := gomonkey.ApplyMethod(reflect.TypeOf(c), "NetworkCompute",func(_ *Computer, a,b int) (int,error) {
            return 2, nil
    })
    
    defer  patches.Reset()

    cp := &Computer{}
    sum, err := cp.Compute(1, 1)
    if sum != 2 || err != nil {
        t.Errorf("expected %v, got %v", 2, sum)
    }
    
}
           

output:

go test -v compute_test.go
=== RUN   TestCompute
--- PASS: TestCompute (0.00s)
PASS
ok  	command-line-arguments	0.006s
           

2.3 mock 一個全局變量

例子中,mock一個全局變量。

// g_test.go
package g

import (
    "testing"

    "github.com/agiledragon/gomonkey"

)

var num = 10

func TestGlobalVar(t *testing.T){
    patches := gomonkey.ApplyGlobalVar(&num, 12)
    defer patches.Reset()
    
    if num != 12 {
        t.Errorf("expected %v, got %v", 12, num)
    }
}
           

output:

go test -v g_test.go
=== RUN   TestGlobalVar
--- PASS: TestGlobalVar (0.00s)
PASS
ok  	command-line-arguments	0.006s
           

2.4 mock 一個函數序列

函數序列主要用在,一個函數被多次調用,每次調用傳回不同值。

// compute_test.go
package g

import (
    "testing"

    "github.com/agiledragon/gomonkey"

)


func compute(a, b int) (int, error){
    return a+b, nil
}

func TestFunc(t *testing.T){
    info1 := "2"
    info2 := "3"
    info3 := "4"
    outputs := []gomonkey.OutputCell{
    	{Values: gomonkey.Params{info1, nil}},// 模拟函數的第1次輸出
    	{Values: gomonkey.Params{info2, nil}},// 模拟函數的第2次輸出
    	{Values: gomonkey.Params{info3, nil}},// 模拟函數的第3次輸出
    }
    patches := gomonkey.ApplyFuncSeq(compute, outputs)
    defer patches.Reset()
    
    output, err := compute(1,1)
    if output != 2 || err != nil {
        t.Errorf("expected %v, got %v", 2, output)
    }
    
    output, err = compute(1,2)
    if output != 3 || err != nil {
        t.Errorf("expected %v, got %v", 2, output)
    }
    
    output, err = compute(1,3)
    if output != 4 || err != nil {
        t.Errorf("expected %v, got %v", 2, output)
    }

}
           

output:

go test -v compute_test.go
=== RUN   TestFunc
--- PASS: TestFunc (0.00s)
PASS
ok  	command-line-arguments	0.006s
           

關于 mock 成員方法序列、函數變量等可以參考github中例子。

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

什麼是内聯?

為了減少函數調用時的堆棧等開銷,對于簡短的函數,會在編譯時,直接内嵌調用的代碼。

請看下面的例子,我們嘗試mock IsEnabled()函數。

package main
import (
    "testing"
    
    "github.com/agiledragon/gomonkey"
)


var flag bool

func IsEnabled() bool{
    return flag
}


func Compute(a, b int) int {
    if IsEnabled(){
        return a+b
    } 

    return a-b
}

func TestCompute(t *testing.T) {
    patches := gomonkey.ApplyFunc(IsEnabled, func() bool{
    return true
    })
    
    defer patches.Reset()

    sum := Compute(1, 1)
    if sum != 2 {
        t.Errorf("expected %v, got %v", 2, sum)
    }
    
}
           

output

go test -v compute_test.go
=== RUN   TestCompute
    TestCompute: compute_test.go:34: expected 2, got 0
--- FAIL: TestCompute (0.00s)
FAIL
FAIL	command-line-arguments	0.007s
FAIL
           

從輸出結果看,測試用例失敗了。

接着,關閉内聯的,再次嘗試:

go test -v -gcflags=-l compute_test.go
=== RUN   TestCompute
--- PASS: TestCompute (0.00s)
PASS
ok  	command-line-arguments	0.006s
           

單元測試通過。

對于 go 1.10以下版本,可使用

-gcflags=-l

禁用内聯,對于go 1.10及以上版本,可以使用

-gcflags=all=-l

。但目前使用下來,都可以。

關于gcflags的用法,可以使用

go tool compile --help

檢視 gcflags 各參數含義。

3.參考

gomonkey

golang單元測試

gomonkey調研文檔和學習

go build 常見編譯優化

Go 語言編譯原理與優化

Go 性能調優之 —— 編譯優化