在Go中模拟接收函数

huangapple go评论81阅读模式
英文:

Mocking receiver functions in Go

问题

我正在尝试对调用结构体中其他接收函数的接收函数进行单元测试。

假设我想测试以下代码中的Three()函数,并模拟对two()函数的调用:

type MyStruct struct {
    a string
    b string
}

func (m *MyStruct) one() int {
    return 2
}

func (m *MyStruct) two() int {
    return m.one() * 2
}

func (m *MyStruct) Three() int {
    return m.two() * 2
}

我正在遵循以下答案中的第二种方法:答案链接

我为每个我想进行单元测试的函数创建了一个自定义构造函数,并用模拟版本覆盖了这些方法。但我认为一旦函数数量增加,代码可能不容易维护。

有没有更好的方法来模拟这样的函数?我希望官方文档中有一些关于如何在不同场景下模拟的指南,类似于Python中的mox库提供的功能。

另外,请注意我不想使用第三方的模拟库。

英文:

I'm trying to unit test a receiver function that calls other receiver functions in that struct.

Let's say I want to test Three() and mock the call to two() in the following:

type MyStruct struct {
	a string
	b string
}

func (m *MyStruct) one() int {
	return 2
}

func (m *MyStruct) two() int {
	return m.one() * 2
}

func (m *MyStruct) Three() int {
	return m.two() * 2
}

I was following method two of the following answer.

I created a custom constructor for every single function that I wanted to unit test and overrode those methods with mocked versions. But I thought it may not be easy to maintain the code once the number of functions grow.

Is there any preferred way of mocking such functions? I wish the official documentation had some guidelines on how to mock things in different scenarios, similar to what mox on Python provides.

Also, note that I don't want to use a third party mocking library.

答案1

得分: 9

这是一种非常不符合惯用方式的测试方法。
在其他语言中可能需要进行模拟,但请不要在Go中这样做。

在你给出的示例中,测试代码的自然方式应该是:1)为MyStruct.one编写一个表驱动测试,并确保测试所有情况。现在你知道one运行得非常好了,2)对MyStruct.two做同样的操作。请注意,在Go中,测试非导出的内容是可能的、有用的和常见的。现在不再需要模拟一些方法,只需3)为MyStruct.Three编写一些表驱动测试并检查其是否正常工作。

但也许你的onetwo方法做了更复杂的事情,并且访问环境(文件系统、数据库、网络),你不希望Three的测试依赖于此?那么请重构你的代码!也许Three不应该是MyStruct的一个方法,而是一个函数,它以interface OneAndTwoer作为参数,你的生产代码使用“真实”的MyStructs调用Three,而测试代码使用不依赖于环境的InMemoryMyStrcuts调用它。你可以称之为模拟,我会称之为接口的不同实现。

在你的示例中,给出的建议很简单:对于onetwoThree使用表驱动测试,不要模拟。
对于一个更现实的问题,建议可能会有所不同,但是不知道具体情况很难给出一般性的建议。最好的一般性建议是:查看标准库中的测试,你会找到几乎适用于每个测试场景的有用模式。

英文:

That is a really un-idiomatic way to test your stuff.
All this mocking might be needed in other languages, but
please don't do it in Go.

The natural way to test your code in the example you gave
would be: 1) Write a table driven test for MyStruct.one
and make sure you test all cases. Now that you know one
works perfectly fine 2) do the same with MyStruct.two.
Note that testing unexported stuff is possible, useful and
common in Go. Now there is no longer a need need to mock
some methods, just 3) write some table driven test for
MyStruct.Three and check it works.

But maybe your methods one and two do fancier stuff, and
access the environment (filesystem, database, network) and
you do not want your tests of Three to depend on that?
So refactor your code! Maybe Three should not be a method
of MyStruct but a function which takes an interface OneAndTwoer
as an argument and your production code calls Three with "real"
MyStructs while your testcode calls it with InMemoryMyStrcuts
which do not depend on the environment? You could call it a
mock, I'd call it a different implementation of an interface.

In your example it is simple to give advice: Use table driven
tests for one, two and Three and do not mock.
For a more realistic problem the advice might be different but
it is hard to give a general advice without knowing the
circumstances. Best general advice is: Take a look at the test in
the standard library where you'll find useful patterns for almost
every testing scenario.

答案2

得分: 2

你可以对代码进行重构,将Three中的参数改为func类型的two,这样你就可以直接模拟输入了。

func main() {
    var ms MyStruct
    fmt.Println(ms.one())
    fmt.Println(ms.two())
    fmt.Println(ms.Three(ms.two))
}

type MyStruct struct {}

func (m *MyStruct) one() int {
    return 2
}

func (m *MyStruct) two() int {
    return m.one() * 2
}

func (m *MyStruct) Three(two func() int) int {
    return two() * 2
}
英文:

You can do a refactor to pass two as a func in Three, so that you can directly mock the input.

func main() {
    var ms MyStruct
    fmt.Println(ms.one())
    fmt.Println(ms.two())
    fmt.Println(ms.Three(ms.two))
}

type MyStruct struct {}

func (m *MyStruct) one() int {
    return 2
}

func (m *MyStruct) two() int {
    return m.one() * 2
}

func (m *MyStruct) Three(two func() int) int {
    return two() * 2
}

huangapple
  • 本文由 发表于 2015年3月25日 10:06:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/29246249.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定