我如何在Go上模拟一个方法?

时间:2015-11-26 19:33:28

标签: go mocking

假设我的某个类型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

}

1 个答案:

答案 0 :(得分:2)

由于Go没有任何形式的继承,您可能无法获得您正在寻找的内容。但是,我认为这些关系有一些替代方法,我觉得它们运作得很好。

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

最初宣布mockFoo时:

type mockFoo struct {
    *foo
}

这不会在两种类型之间创建任何真正的关系。它所做的是推广从foomockFoo的方法。这意味着foo上不在mockFoo上的任何方法都会添加到后者中。这意味着myB.largerInt()myB.foo.largerInt()是相同的调用; foo-> mockFoo之间没有真正的关系可以像你所说的那样使用。

这是故意的 - 与继承相对的构成思想的一部分是,通过限制子组件的交互方式,可以更容易地推断子组件的行为。

那么:你离开了哪里?我会说传统的模拟不会很好地与Go相关,但类似的原则会。而不是通过创建包装器来尝试“子类化”foo,而是必须将模拟目标的所有方法隔离到一个独特的接口中。

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

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

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。具有副作用的任意模拟方法是一个混乱的业务 - 程序本身没有任何东西可以表明什么是有状态的,必须是孤立的,什么不是。事先强制澄清关系可能需要更多的工作,但确实使代码的交互和行为更加明显。