zoukankan      html  css  js  c++  java
  • go 单元测试 gomonkey

    单元测试中,经常需要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 性能调优之 —— 编译优化

    Just try, don't shy.
  • 相关阅读:
    Java中Comparable与Comparator的区别
    LeetCode[5] 最长的回文子串
    LeetCode[3] Longest Substring Without Repeating Characters
    LeetCode 7. Reverse Integer
    统计单词出现的次数
    System.arraycopy()和Arrays.copyOf()的区别
    SyncToy
    查看端口占用及进程号
    TCP协议
    python 的日志logging模块学习
  • 原文地址:https://www.cnblogs.com/lanyangsh/p/14587921.html
Copyright © 2011-2022 走看看