首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >golang源码分析:gomonkey

golang源码分析:gomonkey

作者头像
golangLeetcode
发布2026-03-18 18:10:18
发布2026-03-18 18:10:18
410
举报

mock其实类似json 反序列化,有两种方式,一种是代码生成式的,代表是gomock,前面已经介绍过gomock 源码分析;另一种就是基于反射的源码替换方式,就是本文要介绍的gomonkey,其源码位于:github.com/agiledragon/gomonkey

在分析其源码之前,我们结合例子看看它是如何使用的:

代码语言:javascript
复制
package exp6_test
import (
    "encoding/json"
    "testing"
    . "github.com/agiledragon/gomonkey"
    "github.com/agiledragon/gomonkey/test/fake"
    . "github.com/smartystreets/goconvey/convey"
)
var (
    outputExpect = "xxx-vethName100-yyy"
)
func TestApplyFunc(t *testing.T) {
    Convey("TestApplyFunc", t, func() {
        Convey("one func for succ", func() {
            patches := ApplyFunc(fake.Exec, func(_ string, _ ...string) (string, error) {
                return outputExpect, nil
            })
            defer patches.Reset()
            output, err := fake.Exec("", "")
            So(err, ShouldEqual, nil)
            So(output, ShouldEqual, outputExpect)
        })
        Convey("one func for fail", func() {
            patches := ApplyFunc(fake.Exec, func(_ string, _ ...string) (string, error) {
                return "", fake.ErrActual
            })
            defer patches.Reset()
            output, err := fake.Exec("", "")
            So(err, ShouldEqual, fake.ErrActual)
            So(output, ShouldEqual, "")
        })
        Convey("two funcs", func() {
            patches := ApplyFunc(fake.Exec, func(_ string, _ ...string) (string, error) {
                return outputExpect, nil
            })
            defer patches.Reset()
            patches.ApplyFunc(fake.Belong, func(_ string, _ []string) bool {
                return true
            })
            output, err := fake.Exec("", "")
            So(err, ShouldEqual, nil)
            So(output, ShouldEqual, outputExpect)
            flag := fake.Belong("", nil)
            So(flag, ShouldBeTrue)
        })
        Convey("input and output param", func() {
            patches := ApplyFunc(json.Unmarshal, func(_ []byte, v interface{}) error {
                p := v.(*map[int]int)
                *p = make(map[int]int)
                (*p)[1] = 2
                (*p)[2] = 4
                return nil
            })
            defer patches.Reset()
            var m map[int]int
            err := json.Unmarshal(nil, &m)
            So(err, ShouldEqual, nil)
            So(m[1], ShouldEqual, 2)
            So(m[2], ShouldEqual, 4)
        })
    })
}

测试下

代码语言:javascript
复制
% go test -gcflags="all=-l -N" -v ./test/mockey/exp6/...
=== RUN   TestApplyFunc

  TestApplyFunc 
    one func for succ ✔✔
    one func for fail ✔✔
    two funcs ✔✔✔
    input and output param ✔✔✔


10 total assertions

--- PASS: TestApplyFunc (0.00s)
PASS

它一般和convey配合使用,由于gomonkey是基于符号表对函数指针进行替换来实现mock的,所以需要禁止内联优化

代码语言:javascript
复制
go test -gcflags="all=-l -N"

它的核心方法如下,首先是进行方法函数/方法进行mock,ApplyMethod 接口定义如下:

代码语言:javascript
复制
func ApplyMethod(target reflect.Type, methodName string, double interface{}) *Patches
func (this *Patches) ApplyMethod(target reflect.Type, methodName string, double interface{}) *Patches

ApplyMethod 第一个参数是目标类的指针变量的反射类型,第二个参数是字符串形式的方法名,第三个参数是桩函数。测试完成后,patches 对象通过 Reset 成员方法删除所有测试桩。

接着是mock变量和函数变量,ApplyGlobalVar 接口定义如下:

代码语言:javascript
复制
func ApplyGlobalVar(target, double interface{}) *Patches
func (this *Patches) ApplyGlobalVar(target, double interface{}) *Patches

ApplyGlobalVar 第一个参数是全局变量的地址,第二个参数是全局变量的桩。测试完成后,patches 对象通过 Reset 成员方法删除所有测试桩。

ApplyFuncVar 接口定义如下:

代码语言:javascript
复制
func ApplyFuncVar(target, double interface{}) *Patches
func (this *Patches) ApplyFuncVar(target, double interface{}) *Patches

ApplyFuncVar 第一个参数是函数变量的地址,第二个参数是桩函数。测试完成后,patches 对象通过 Reset 成员方法删除所有测试桩。

对应的,还有三个批量函数,ApplyFuncSeq 第一个参数是函数名,第二个参数是特定的桩序列参数。

代码语言:javascript
复制
func ApplyFuncSeq(target interface{}, outputs []OutputCell) *Patches
func (this *Patches) ApplyFuncSeq(target interface{}, outputs []OutputCell) *Patches

其中,OutputCell 的定义为:

代码语言:javascript
复制
type Params []interface{}
type OutputCell struct {
    Values Params
    Times  int
}

测试完成后,patches 对象通过 Reset 成员方法删除所有测试桩。在单元测试中对一个函数进行多次调用并返回不同的结果。它适用于那些在测试中需要模拟函数在循环中被多次调用的场景。

代码语言:javascript
复制
    patches := gomonkey.NewPatches()
    defer patches.Reset()
    // 定义桩函数序列patches.ApplyFuncSeq(funcA, []OutputCell{
            {Values: Params{1}},
            {Values: Params{2}},
            {Values: Params{3}},
        })

ApplyMethodSeq 接口定义如下:

代码语言:javascript
复制
func ApplyMethodSeq(target reflect.Type, methodName string, outputs []OutputCell) *Patches
func (this *Patches) ApplyMethodSeq(target reflect.Type, methodName string, outputs []OutputCell) *Patches

ApplyMethodSeq 第一个参数是目标类的指针变量的反射类型,第二个参数是字符串形式的方法名,第三参数是特定的桩序列参数。测试完成后,patches 对象通过 Reset 成员方法删除所有测试桩。

ApplyFuncVarSeq 接口定义如下:

代码语言:javascript
复制
func ApplyFuncVarSeq(target interface{}, outputs []OutputCell) *Patches
func (this *Patches) ApplyFuncVarSeq(target interface{}, outputs []OutputCell) *Patches

ApplyFuncVarSeq 第一个参数是函数变量地址,第二个参数是特定的桩序列参数。测试完成后,patches 对象通过 Reset 成员方法删除所有测试桩。

核心方法介绍完了,最后我们看下创建打桩的方法:

代码语言:javascript
复制
func NewPatches() *Patches

NewPatches 是 patches 对象的显式构造函数,一般用于目标和桩的表驱动场景。测试完成后,patches 对象通过 Reset 成员方法删除所有测试桩。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-06-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 golang算法架构leetcode技术php 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档