流绪潜微梦 發表於 2021-3-28 10:18:00

go 单元测试 gomonkey

<p></p><div class="toc"><div class="toc-container-header">目录</div><ul><li>1.安装</li><li>2.使用方法<ul><li>2.1 mock一个函数</li><li>2.2 mock 一个方法</li><li>2.3 mock 一个全局变量</li><li>2.4 mock 一个函数序列</li></ul></li><li>3.参考</li></ul></div><p></p>
<p>单元测试中,经常需要mock。</p>
<p>例如,一个函数中,需要调用网络连接函数建立连接。做单元测试时,这个建立连接的函数就可以mock一下,而不真正去尝试建立连接。</p>
<p>mock 有时也称为“打桩”。<br>
例如,mock一个函数,可以说,为一个函数打桩。</p>
<p>在golang中,<br>
gomonkey 就是这样的工具库。</p>
<p>本文主要介绍使用gomonkey进行mock。</p>
<h1 id="1安装">1.安装</h1>
<pre><code>$ go get github.com/agiledragon/gomonkey
</code></pre>
<h1 id="2使用方法">2.使用方法</h1>
<h2 id="21-mock一个函数">2.1 mock一个函数</h2>
<p>下面例子中,调用链是:Compute()--&gt; networkCompute()。本地单测时,一般不会建立网络连接,因此需要mocknetWorkCompute()。</p>
<pre><code class="language-go">//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)
    }
   
}
</code></pre>
<p>output:</p>
<pre><code>go test -v compute_test.go
=== RUN   TestCompute
--- PASS: TestCompute (0.00s)
PASS
ok        command-line-arguments        0.006s
</code></pre>
<p>上面代码中,我们mock 了 networkCompute(),返回了计算结果2。</p>
<p>再例如:</p>
<p>下面代码中,调用链:Convert2Json() --&gt; json.Marshal()</p>
<p>尝试mock json.Marshal()。</p>
<pre><code>// 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(&amp;h)

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

</code></pre>
<p>output:</p>
<pre><code>go test -v json_test.go
=== RUN   TestConvert2Json
--- PASS: TestConvert2Json (0.00s)
PASS
ok        command-line-arguments        0.006s
</code></pre>
<h2 id="22-mock-一个方法">2.2 mock 一个方法</h2>
<p>例子中,定义一个类型,类型中有两个方法Compute(), NetworkCompute(),调用关系为:Compute()--&gt;NetworkCompute()。</p>
<pre><code class="language-go">//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
    })
   
    deferpatches.Reset()

    cp := &amp;Computer{}
    sum, err := cp.Compute(1, 1)
    if sum != 2 || err != nil {
      t.Errorf("expected %v, got %v", 2, sum)
    }
   
}
</code></pre>
<p>output:</p>
<pre><code> go test -v compute_test.go
=== RUN   TestCompute
--- PASS: TestCompute (0.00s)
PASS
ok        command-line-arguments        0.006s
</code></pre>
<h2 id="23-mock-一个全局变量">2.3 mock 一个全局变量</h2>
<p>例子中,mock一个全局变量。</p>
<pre><code>// g_test.go
package g

import (
    "testing"

    "github.com/agiledragon/gomonkey"

)

var num = 10

func TestGlobalVar(t *testing.T){
    patches := gomonkey.ApplyGlobalVar(&amp;num, 12)
    defer patches.Reset()
   
    if num != 12 {
      t.Errorf("expected %v, got %v", 12, num)
    }
}
</code></pre>
<p>output:</p>
<pre><code>go test -v g_test.go
=== RUN   TestGlobalVar
--- PASS: TestGlobalVar (0.00s)
PASS
ok        command-line-arguments        0.006s
</code></pre>
<h2 id="24-mock-一个函数序列">2.4 mock 一个函数序列</h2>
<p>函数序列主要用在,一个函数被多次调用,每次调用返回不同值。</p>
<pre><code>// 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)
    }

}
</code></pre>
<p>output:</p>
<pre><code>go test -v compute_test.go
=== RUN   TestFunc
--- PASS: TestFunc (0.00s)
PASS
ok        command-line-arguments        0.006s
</code></pre>
<p>关于 mock 成员方法序列、函数变量等可以参考github中例子。</p>
<p>有时会遇到mock失效的情况,这个问题一般是内联导致的。</p>
<p>什么是内联?</p>
<p>为了减少函数调用时的堆栈等开销,对于简短的函数,会在编译时,直接内嵌调用的代码。</p>
<p>请看下面的例子,我们尝试mock IsEnabled()函数。</p>
<pre><code>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)
    }
   
}
</code></pre>
<p>output</p>
<pre><code>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
</code></pre>
<p>从输出结果看,测试用例失败了。</p>
<p>接着,关闭内联的,再次尝试:</p>
<pre><code>go test -v -gcflags=-l compute_test.go
=== RUN   TestCompute
--- PASS: TestCompute (0.00s)
PASS
ok        command-line-arguments        0.006s
</code></pre>
<p>单元测试通过。</p>
<p>对于 go 1.10以下版本,可使用<code>-gcflags=-l</code>禁用内联,对于go 1.10及以上版本,可以使用<code>-gcflags=all=-l</code>。但目前使用下来,都可以。</p>
<p>关于gcflags的用法,可以使用 <code>go tool compile --help</code> 查看 gcflags 各参数含义。</p>
<p>另外需要注意的:<br>
如果创建新的goroutine,在新goroutine中执行的代码,原来的mock不会生效。</p>
<h1 id="3参考">3.参考</h1>
<p>gomonkey</p>
<p>golang单元测试</p>
<p>gomonkey调研文档和学习</p>
<p>go build 常见编译优化</p>
<p>Go 语言编译原理与优化</p>
<p>Go 性能调优之 —— 编译优化</p>


</div>
<div id="MySignature" role="contentinfo">
    Just try, don't shy.<br><br>
来源:https://www.cnblogs.com/lanyangsh/p/14587921.html
頁: [1]
查看完整版本: go 单元测试 gomonkey