Go 如何在运行中模拟方法?

Go 如何在运行中模拟方法?,go,mocking,Go,Mocking,假设我有一个类型foo,其方法largerInt()调用largeInt()。我想测试largeInt(),所以我需要模拟largeInt(),因为可能有副作用 然而,我没有做到这一点。使用接口和组合,我可以模拟largeInt(),但在largerInt()内部,它似乎不可修改,因为调用它时,没有对包装类型的引用 你知道怎么做吗?下面是我创建的一个片段来说明这个问题 谢谢 package main import ( "fmt" ) type foo struct { } type

假设我有一个类型
foo
,其方法
largerInt()
调用
largeInt()
。我想测试
largeInt()
,所以我需要模拟
largeInt()
,因为可能有副作用

然而,我没有做到这一点。使用接口和组合,我可以模拟
largeInt()
,但在
largerInt()
内部,它似乎不可修改,因为调用它时,没有对包装类型的引用

你知道怎么做吗?下面是我创建的一个片段来说明这个问题

谢谢

package main

import (
    "fmt"
)

type foo struct {
}

type mockFoo struct {
    *foo
}

type MyInterface interface {
    largeInt() int
}

func standaloneLargerInt(obj MyInterface) int {
    return obj.largeInt() + 10
}

func (this *foo) largeInt() int {
    return 42
}

func (this *mockFoo) largeInt() int {
    return 43
}

func (this *foo) largerInt() int {
    return this.largeInt() + 10
}

func main() {
    myA := &foo{}
    myB := &mockFoo{}
    fmt.Printf("%s\n", standaloneLargerInt(myA)) // 52
    fmt.Printf("%s\n", standaloneLargerInt(myB)) // 53

    fmt.Printf("%s\n", myA.largerInt()) // 52
    fmt.Printf("%s\n", myB.largerInt()) // 52

}

由于Go没有任何形式的继承,您可能无法获得您想要的东西。然而,我发现有一些替代方法可以很好地处理这类关系

但首先,让我们深入研究一下你的代码中到底发生了什么。您可能已经知道了其中的大部分内容,但重复它可能会使行为更加明显:

最初声明
mockFoo
时:

type mockFoo struct {
    *foo
}
type foo struct {
    fooMethods
}

type fooMethods interface {
    largeInt() int
}

func (this *foo) largerInt() int {
    return this.largeInt() + 10
}

type fooMethodsStd struct{}

func (this *fooMethodsStd) largeInt() int {
    return 42
}

var defaultFooMethods = &fooMethodsStd{}

type fooMethodsMock struct{}

func (this *fooMethodsMock) largeInt() int {
    return 43
}

var mockedFooMethods = &fooMethodsMock{}

func main() {
    normal := foo{defaultFooMethods}
    mocked := foo{mockedFooMethods}

    fmt.Println(normal.largerInt()) // 52
    fmt.Println(mocked.largerInt()) // 53
}
这不会在这两种类型之间创建任何真正的关系。它所做的是将方法从
foo
提升到
mockFoo
。这意味着
foo
上的任何方法如果不在
mockFoo
上,都将添加到后者中。这意味着
myB.largerInt()
myB.foo.largerInt()
是相同的调用;从foo->mockFoo中没有真正的关系可以像你所说的那样使用

这是有意为之的——与继承相反的组合思想的一部分是,它通过限制子组件的交互方式,使子组件的行为更容易推理


那么:这会给你留下什么?我想说,传统的模拟将不会很好地移植到围棋中,但类似的原则也会。不要试图通过创建包装器来“子类化”foo,您必须将模拟目标的所有方法隔离到一个不同的接口中

但是:如果你想在
foo
上测试没有副作用的方法呢?您已经找到了一种替代方法:将所有希望测试的功能放在单独的静态方法中。然后
foo
可以将其所有静态行为委托给它们,它们将非常容易测试

还有其他更类似于您所布置的结构的选项。例如,您可以反转
mockFoo
foo
之间的关系:

type mockFoo struct {
    *foo
}
type foo struct {
    fooMethods
}

type fooMethods interface {
    largeInt() int
}

func (this *foo) largerInt() int {
    return this.largeInt() + 10
}

type fooMethodsStd struct{}

func (this *fooMethodsStd) largeInt() int {
    return 42
}

var defaultFooMethods = &fooMethodsStd{}

type fooMethodsMock struct{}

func (this *fooMethodsMock) largeInt() int {
    return 43
}

var mockedFooMethods = &fooMethodsMock{}

func main() {
    normal := foo{defaultFooMethods}
    mocked := foo{mockedFooMethods}

    fmt.Println(normal.largerInt()) // 52
    fmt.Println(mocked.largerInt()) // 53
}
然后,您“插入”结构的有状态组件,而不是通过继承来管理它。然后在运行时将if设置为
defaultFooMethods
,并使用模拟版本进行测试。由于结构中缺少默认值,这有点烦人,但它可以工作


对于那些喜欢组合而不是继承的人来说,这是一个特性,而不是一个bug。任意地模仿有副作用的方法是一件麻烦的事情——程序本身没有任何东西表明什么是有状态的,什么必须被隔离,什么不是。事先强制澄清关系可能需要更多的工作,但确实会使代码的交互和行为更加明显