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。任意地模仿有副作用的方法是一件麻烦的事情——程序本身没有任何东西表明什么是有状态的,什么必须被隔离,什么不是。事先强制澄清关系可能需要更多的工作,但确实会使代码的交互和行为更加明显