agiledragon / gomonkey

gomonkey is a library to make monkey patching in unit tests easy
MIT License
1.93k stars 179 forks source link

单例模式无法进行成员方法打桩 #42

Closed zhijian-pro closed 2 years ago

zhijian-pro commented 3 years ago

我写了一个小 demo 模拟这个问题 首先目录结构如下图 image

persion.go 代码如下

package model

import (
    "strings"
)

/*
 @Author: zhijian
 @Date: 2021/3/29 18:54
 @Description:
*/

//单例变量
var One = &Persion{Name: "test"}

//类型为公开的没有任何问题  
//可是如果这里为私有 persion 那么 ApplyMethod 最后一个参数 double interface{} 因为不在同一个包里面 就会取不到方法的 receiver 的类型 
//但是为私有 persion 的时候 ApplyMethodSeq  因为不需要显式的填这个取不到的类型 编译可以通过,可以正常使用
//这个地方有比较优雅的解决方案吗?还是我使用的姿势不对?
type Persion struct {
    Name string
}

func (p *Persion) Echo(age int) (string,error) {
    var sb strings.Builder
    sb.WriteString(p.Name)
    sb.WriteString(string(rune(age)))
    return sb.String(), nil
}

func (p *Persion) EchoMaybeDiff(age int) (string,error) {
    var sb strings.Builder
    sb.WriteString(p.Name)
    sb.WriteString(string(rune(age)))
    return sb.String(), nil
}

svc.go 代码如下

package svc

import "dtstack.com/dtstack/easymatrix/matrix/mock/model"

/*
 @Author: zhijian
 @Date: 2021/3/29 19:08
 @Description:
*/

func BizEcho() string  {
    echo, err := model.One.Echo(3)
    if err != nil {
        panic(err)
    }
    return echo
}

func BizEchoMaybeDiff() string  {
    echo, err := model.One.EchoMaybeDiff(3)
    if err != nil {
        panic(err)
    }
    return echo
}

svc_test.go 代码如下

package svc

import (
    "dtstack.com/dtstack/easymatrix/matrix/mock/model"
    "fmt"
    . "github.com/agiledragon/gomonkey"
    . "github.com/smartystreets/goconvey/convey"
    "reflect"
    "testing"
)

/*
 @Author: zhijian
 @Date: 2021/3/29 19:18
 @Description:
*/

func TestEchoMaybeDiff(t *testing.T) {
    Convey("test",t,
        func() {
            outputs := []OutputCell{
                {Values: Params{`test1`, nil}},// 模拟函数的第1次输出

                {Values: Params{`test2`, nil}},// 模拟函数的第2次输出

                {Values: Params{`test3`, nil}},// 模拟函数的第3次输出
            }
            patches := ApplyMethodSeq(reflect.TypeOf(model.One), "EchoMaybeDiff", outputs)
            defer patches.Reset()
            echo1 := BizEchoMaybeDiff()
            echo2 :=BizEchoMaybeDiff()
            echo3 :=BizEchoMaybeDiff()
            fmt.Println(echo1)
            fmt.Println(echo2)
            fmt.Println(echo3)
            ShouldNotBeNil(echo3)
        })
}

func TestBizEcho(t *testing.T) {
    Convey("test",t,
        func() {
            patches := ApplyMethod(reflect.TypeOf(model.One), "Echo", func(_ *model.Persion, i int) (string, error) {
                return "test", nil
            })
            defer patches.Reset()
            bizEcho := BizEcho()
            fmt.Println(bizEcho)
            ShouldEqual(bizEcho, "test")
        })
}

我觉得这种单例模式写法应该挺常见的,是不是我使用的姿势不对?

agiledragon commented 3 years ago

ApplyMethod 和 ApplyMethodSeq 对于第三个参数的处理有些细节上的差异:(1)ApplyMethod第三个参数为一个匿名函数,用户可定制能力强,可以校验入参,赋值出参;(2)ApplyMethodSeq第三个参数为一个切片,这个切片元素就是make一个动态函数的返回值,而这个动态函数忽略入参和出参,仅能打桩返回值。 对于打桩的类型,建议定义为public。

zhoxife3ng commented 3 years ago

有可能是你的Echo方法太短被内联了,看一下readme的第一条notes,试试

go test -gcflags=all=-l -v svc_test.go -test.run TestBizEcho

zhijian-pro commented 2 years ago

@agiledragon 能否解释下为什么很多方法都需要传一个匿名函数作为参数,我看代码中这个参数的作用是进行类型比对,事实上我更改部分代码,去掉了这部分的类型校验,单例模式的打桩就可以用了,那是不是所有的函数中的这个匿名函数参数都可以去掉那?这个类型的比对的原因是什么那?