假设我的某个类型foo
的方法largerInt()
调用largeInt()
。我想测试largerInt()
,因此我需要模拟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
}
答案 0 :(得分:2)
由于Go没有任何形式的继承,您可能无法获得您正在寻找的内容。但是,我认为这些关系有一些替代方法,我觉得它们运作得很好。
但首先,让我们深入了解一下代码中究竟发生了什么。您可能已经知道了大部分内容,但重申它可能会使行为更加明显:
最初宣布mockFoo
时:
type mockFoo struct {
*foo
}
这不会在两种类型之间创建任何真正的关系。它所做的是推广从foo
到mockFoo
的方法。这意味着foo
上不在mockFoo
上的任何方法都会添加到后者中。这意味着myB.largerInt()
和myB.foo.largerInt()
是相同的调用; foo-> mockFoo之间没有真正的关系可以像你所说的那样使用。
这是故意的 - 与继承相对的构成思想的一部分是,通过限制子组件的交互方式,可以更容易地推断子组件的行为。
那么:你离开了哪里?我会说传统的模拟不会很好地与Go相关,但类似的原则会。而不是通过创建包装器来尝试“子类化”foo,而是必须将模拟目标的所有方法隔离到一个独特的接口中。
但是:如果你想在foo
上测试没有副作用的方法怎么办?您已经找到了一种替代方法:将您希望测试的所有功能放在单独的静态方法中。然后foo
可以将它的所有静态行为委托给它们,并且它们将很容易测试。
还有其他选项更类似于您布置的结构。例如,您可以反转mockFoo
和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。具有副作用的任意模拟方法是一个混乱的业务 - 程序本身没有任何东西可以表明什么是有状态的,必须是孤立的,什么不是。事先强制澄清关系可能需要更多的工作,但确实使代码的交互和行为更加明显。