Gomonkey测试框架

June 27, 2022

这篇文章介绍了Golang的单元测试工具Gomonkey。Gomonkey支持为函数、成员方法、函数变量、接口和全局变量打桩。文章详细展示了如何使用Gomonkey进行单元测试,并列出了可能导致打桩失败的原因。同时,文章还解释了什么是内联,并展示了如何禁用内联进行测试。

功能列表

  • 支持为一个函数打一个桩
  • 支持为一个函数打一个特定的桩序列
  • 支持为一个成员方法打一个桩
  • 支持为一个成员方法打一个特定的桩序列
  • 支持为一个函数变量打一个桩
  • 支持为一个函数变量打一个特定的桩序列
  • 支持为一个接口打桩
  • 支持为一个接口打一个特定的桩序列
  • 支持为一个全局变量打一个桩

打桩失败的可能原因

  • gomonkey 不是并发安全的。如果有多协程并发对同一个目标的打桩的情况,则需要将之前的协程先优雅退出。
  • 打桩目标为内联的函数或成员方法。可通过命令行参数-gcflags=-l(go1.10 版本之前)或 -gcflags=all=-l(go1.10 版本及之后)关闭内联优化。
  • gomonkey 对于私有成员方法的打桩失败。go1.6 版本的反射机制支持私有成员方法的查询,而 go1.7 及之后的版本却不支持,所以当用户使用 go1.7 及之后的版本时,gomonkey 对于私有成员方法的打桩会触发异常。

示例

package monkey

import (
	"fmt"
	"reflect"
	"testing"

	"github.com/agiledragon/gomonkey"
	. "github.com/smartystreets/goconvey/convey"
)

// ###############################################################
// ########################## ApplyFunc ##########################
// ###############################################################

func logicFunc(a, b int) (int, error) {
	sum, err := netWorkFunc(a, b)
	if err != nil {
		return 0, err
	}

	return sum, nil
}

func netWorkFunc(a, b int) (int, error) {
	if a < 0 && b < 0 {
		errmsg := "a<0 && b<0" //gomonkey有bug,函数一定要有栈分配变量,不然mock不住
		return 0, fmt.Errorf("%v", errmsg)
	}

	return a + b, nil
}

func TestApplyFunc(t *testing.T) {
	Convey("Test ApplyFunc: ", t, func() {

		// 为函数打桩
		var patches = gomonkey.ApplyFunc(netWorkFunc, func(a, b int) (int, error) {
			return 20, nil
		})
		defer patches.Reset()

		sum, err := logicFunc(10, 20) // logicFunc中调用了netWorkFunc

		So(sum, ShouldEqual, 20)
		So(err, ShouldBeNil)
	})
}

// ###############################################################
// ######################### ApplyMethod #########################
// ###############################################################

type myType struct {
}

func (m *myType) logicFunc(a, b int) (int, error) {
	sum, err := m.NetWorkFunc(a, b)
	if err != nil {
		return 0, err
	}
	return sum, nil
}

func (m *myType) NetWorkFunc(a, b int) (int, error) {
	if a < 0 && b < 0 {
		errmsg := "a<0 && b<0"
		return 0, fmt.Errorf("%v", errmsg)
	}

	return a + b, nil
}

func TestApplyMethod(t *testing.T) {
	Convey("Test ApplyMethod: ", t, func() {
		var p *myType
		patches := gomonkey.ApplyMethod(reflect.TypeOf(p), "NetWorkFunc", func(_ *myType, a, b int) (int, error) {
			return 20, nil
		})
		defer patches.Reset()

		var m myType
		sum, err := m.logicFunc(10, 20)
		So(sum, ShouldEqual, 20)
		So(err, ShouldBeNil)
	})
}


// ###############################################################
// ######################### ApplyGlobalVar ######################
// ###############################################################

var num = 10

func TestApplyGlobalVar(t *testing.T) {
    Convey("Test ApplyGlobalVar: ", t, func() {

        Convey("change", func() {
            patches := gomonkey.ApplyGlobalVar(&num, 150)
            defer patches.Reset()
            So(num, ShouldEqual, 150)
        })

        Convey("recover", func() {
            So(num, ShouldEqual, 10)
        })
    })
}

// ###############################################################
// ########################## ApplyFuncSeq #######################
// ###############################################################


func getInt() (int) {
    a := 100
    return a
}

func TestMockFuncSeq(t *testing.T) {
    Convey("Test ApplyFuncSeq: ", t, func() {
        outputs := []gomonkey.OutputCell{
            {Values:gomonkey.Params{2}, Times:1},
            {Values:gomonkey.Params{1}, Times:0},
            {Values:gomonkey.Params{3}, Times:2},
        }
        var p1 = gomonkey.ApplyFuncSeq(getInt, outputs)
        defer p1.Reset()

        So(getInt(), ShouldEqual, 2)
        So(getInt(), ShouldEqual, 1)
        So(getInt(), ShouldEqual, 3)
        So(getInt(), ShouldEqual, 3)
    })

	Convey("Test ApplyFuncSeq: ", t, func ()  {
		So(getInt(), ShouldEqual, 100)
	})
}

执行命令

[iarno@nd01v monkey]$ go test -v -gcflags=-l .
=== RUN   TestApplyFunc

  Test ApplyFunc: ✔✔


2 total assertions

--- PASS: TestApplyFunc (0.00s)
=== RUN   TestApplyMethod

  Test ApplyMethod: ✔✔


4 total assertions

--- PASS: TestApplyMethod (0.00s)
=== RUN   TestApplyGlobalVar

  Test ApplyGlobalVar: 
    change 
    recover 


6 total assertions

--- PASS: TestApplyGlobalVar (0.00s)
=== RUN   TestMockFuncSeq

  Test ApplyFuncSeq: ✔✔✔✔


10 total assertions


  Test ApplyFuncSeq: 


11 total assertions

--- PASS: TestMockFuncSeq (0.00s)
PASS
ok      hellogolang/monkey      (cached)

什么是内联

为了减少函数调用时的堆栈等开销,对于简短的函数,会在编译时,直接内嵌调用的代码。

我们禁用下内联,然后执行, go test -v -gcflags=-l monkey_test.go

参考

https://github.com/agiledragon/gomonkey

https://juejin.cn/post/7111691109528502286

Go

IARNO

服务端开发

Dig 命令使用详解

GoConvey测试框架