栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Go语言

Golang:gomonkey ApplyMethod 失效解决方式

Go语言 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Golang:gomonkey ApplyMethod 失效解决方式


文章目录
    • 1.问题起源
    • 2.解决方式
      • 1.Goland配置test禁止内联优化
    • 2.修改指针/结构体调用

1.问题起源

今天想对之前匆匆写下的代码完善单元测试,使用了gomonkey对代码中的http调用和rpc调用进行打桩,核心的待测代码和测试代码如下:

  • utils.go
package utils

type MaterialUtil interface {
   QueryTaskByTaskId(ctx context.Context, centerTaskId int64) (resp *MaterialTask, err error)
}
type MaterialUtilImpl struct {
}

func (m MaterialUtilImpl) QueryTaskByTaskId(ctx context.Context, centerTaskId int64) (resp *MaterialTask, err error) {
   // 这里有一堆http和rpc调用
   resp, err = getMaterialsByTaskId(centerTaskId)
   // 忽略...
   return resp, err
}
  • material_test.go
package material_test

import (
   "context"
   "github.com/agiledragon/gomonkey"
   . "github.com/smartystreets/goconvey/convey"
   "reflect"
   "testing"
   "utils"
)

func TestMaterialCenterTask(t *testing.T) {
   Convey("test material task center", t, func() {
      // 使用gomonkey mock掉MaterialUtilImpl的QueryTaskByTaskId方法
      gomonkey.ApplyMethod(reflect.TypeOf(&utils.MaterialUtilImpl{}), "QueryTaskByTaskId", func(_ *utils.MaterialUtilImpl, ctx context.Context, centerTaskId int64) (resp *utils.MaterialTask, err error) {
         return &utils.MaterialTask{
            ErrCode: 0,
            Msg:     "mock调用成功",
         }, nil
      })
      // 测试
      materialUtil := &utils.MaterialUtilImpl{}
      resp, err := materialUtil.QueryTaskByTaskId(context.TODO(), 12345)
      So(err, ShouldBeNil)
      So(resp.Msg, ShouldEqual, "mock调用成功")
   })
}

单测不通过,报错,说明gomonkey ApplyMethod不生效,没有正常mock MaterialUtilImpl的QueryTaskByTaskId方法。

Failures:
  Expected: 'mock调用成功'
  Actual:   'null'
  (Should be equal)
2.解决方式 1.Goland配置test禁止内联优化

gomonkey对于mock方法失败的官方解决方式是启动go test时新增启动参数-gcflags=all=-l避免内联优化。
gomonkey的实现原理是获取目标函数的入口地址并将跳转至替换函数的机器码填充在目标函数的地址处,这样调用目标函数时,从目标函数的入口地址进入会直接执行go monkey填充的跳转指令,跳转到替换函数处继续执行。因此,需要在调用目标函数时,跳转至目标函数的入口地址,才可以执行go monkey填充的跳转指令。而golang编译器在编译时会进行内联优化,即把简短的函数在调用它的地方展开,从而消除调用目标函数的开销。但因为内联消除了调用目标函数时的跳转操作,使得go monkey填充在目标函数入口处的指令无法执行,因而也无法实现函数体的运行时替换,使go monkey失效。

使用goland的话在Run/Edit Configuraitons中也可以新增这个启动参数,在执行测试时附加上:

2.修改指针/结构体调用

做完上一步后发现还是不行,再次看文档示例发现一个不一样的地方,就是示例中默认认为大家都是通过结构体指针调用方法,而不是通过结构体调用方法的,但是在我们的代码实现中是会出现通过结构体调用方法的,这就是问题所在:

使用ApplyMethod时,reflect.TypeOf(caller)的caller入参和func(_ caller)的caller入参必须和原方法一致,原方法采用的是结构体调用,那么caller就必须为结构体,反之就都得为指针。

所以解决方式有两种,一种是修改原接口为指针调用,那么上述test程序就可以正确mock了,第二种是在ApplyMethod时采用结构体作为入参:

  • material_test.go
package material_test

import (
   "context"
   "github.com/agiledragon/gomonkey"
   . "github.com/smartystreets/goconvey/convey"
   "reflect"
   "testing"
   "utils"
)

func TestMaterialCenterTask(t *testing.T) {
   Convey("test material task center", t, func() {
      // 以MaterialUtilImpl结构体作为入参
      gomonkey.ApplyMethod(reflect.TypeOf(utils.MaterialUtilImpl{}), "QueryTaskByTaskId", func(_ utils.MaterialUtilImpl, ctx context.Context, centerTaskId int64) (resp *utils.MaterialTask, err error) {
         return &utils.MaterialTask{
            ErrCode: 0,
            Msg:     "mock调用成功",
         }, nil
      })
      // 测试
      materialUtil := &utils.MaterialUtilImpl{}
      resp, err := materialUtil.QueryTaskByTaskId(context.TODO(), 12345)
      So(err, ShouldBeNil)
      So(resp.Msg, ShouldEqual, "mock调用成功")
   })
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/904816.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号