我对依赖关系感到困惑。我希望能够用mock函数调用替换一些函数调用。下面是我的代码片段:
func get_page(url string) string {
get_dl_slot(url)
defer free_dl_slot(url)
resp, err := http.Get(url)
if err != nil { return "" }
defer resp.Body.Close()
contents, err := ioutil.ReadAll(resp.Body)
if err != nil { return "" }
return string(contents)
}
func downloader() {
dl_slots = make(chan bool, DL_SLOT_AMOUNT) // Init the download slot semaphore
content := get_page(BASE_URL)
links_regexp := regexp.MustCompile(LIST_LINK_REGEXP)
matches := links_regexp.FindAllStringSubmatch(content, -1)
for _, match := range matches{
go serie_dl(match[1], match[2])
}
}
字符串
我希望能够测试downloader()
,而不实际通过http获取页面-即通过模拟get_page
(更容易,因为它只返回字符串形式的页面内容)或http.Get()
。
我发现这个线程似乎是关于一个类似的问题。朱利安菲利普斯提出了他的库,Withmock作为一个解决方案,但我无法让它工作。这里是我的测试代码的相关部分,这主要是货物邪教代码给我,说实话:
import (
"testing"
"net/http" // mock
"code.google.com/p/gomock"
)
...
func TestDownloader (t *testing.T) {
ctrl := gomock.NewController()
defer ctrl.Finish()
http.MOCK().SetController(ctrl)
http.EXPECT().Get(BASE_URL)
downloader()
// The rest to be written
}
型
测试输出如下:
错误:无法安装'_et/http':exit status 1输出:无法加载包:package _et/http:在中找到包http(chunked.go)和main(main_mock.go)
/var/folders/z9/ql_yn5h550s6shtb9c5sggj40000gn/T/withmock570825607/path/src/_et/http
Withmock可以解决我的测试问题吗?我应该怎么做才能让它工作?
9条答案
按热度按时间vnzz0bqm1#
就我个人而言,我不使用
gomock
(或任何mocking框架;没有它,Go中的mocking非常容易)。我要么将依赖项作为参数传递给downloader()
函数,要么将downloader()
作为一个类型上的方法,类型可以保存get_page
依赖项:方法一:将
get_page()
作为downloader()
的参数传入字符串
主要内容:
型
测试:
型
方法2:将
download()
设为Downloader
类型的方法:如果你不想把依赖项作为参数传递,你也可以让
get_page()
成为一个类型的成员,让download()
成为该类型的方法,然后它可以使用get_page
:型
主要内容:
型
测试:
型
up9lanfz2#
如果您更改函数定义以使用变量:
字符串
您可以在测试中覆盖它:
型
不过要小心,如果其他测试测试的是您重写的函数的功能,那么它们可能会失败!
Go语言的作者在Go语言标准库中使用这种模式来将测试钩子插入代码中,以使测试变得更容易:
8ehkhllq3#
我使用了一种稍微不同的方法,其中publicstruct方法实现了interface,但它们的逻辑仅限于 Package private(未导出)函数,这些函数将这些interface作为参数。这为您提供了模拟几乎任何依赖项所需的粒度,同时还拥有一个干净的API,可以从测试套件外部使用。
要理解这一点,必须理解您可以访问测试用例中未导出的方法(即从您的
_test.go
文件中),因此您可以测试这些方法,而不是测试导出的方法,这些方法除了 Package 之外没有任何逻辑。总结:测试未导出的函数,而不是测试导出的函数!
让我们举个例子。假设我们有一个Slack API结构体,它有两个方法:
SendMessage
方法,用于向Slack webhook发送HTTP请求SendDataSynchronously
方法给出了一个字符串切片,它迭代这些字符串,并在每次迭代时调用SendMessage
因此,为了测试
SendDataSynchronously
而不每次都发出HTTP请求,我们必须模拟SendMessage
,对吗?字符串
我喜欢这种方法的原因是,通过查看未导出的方法,您可以清楚地看到依赖项是什么。同时,您导出的API更清晰,需要传递的参数更少,因为这里真正的依赖项只是父接收器,它实现了所有这些接口本身。然而,每个函数都可能只依赖于其中的一部分(一个,也许是两个接口),这使得重构变得容易得多。很高兴看到你的代码是如何真正耦合的,只要看看函数签名,我认为它是一个强大的工具,可以防止闻到代码。
为了方便起见,我将所有内容都放在一个文件中,以便您在playground here中运行代码,但我建议您也在GitHub上查看完整的示例,这里是slack.go文件,这里是slack_test.go。
here整个事情。
bz4sfanl4#
我会这样做,
主要
字符串
测试
型
我会避免在golang中使用
_
。最好使用camelCasec9qzyr3d5#
最简单的方法是将函数设置为全局变量,并在测试之前设置自定义方法
字符串
在测试中,必须首先模拟该全局变量,
型
另一种方法是将RandomStringGenerator作为依赖项传递,并将其存储在
TeamManagerService
中,并像这样使用它:型
在测试中,您可以使用自己的自定义函数,
型
你像我一样使用证明:D你可以做到这一点
型
在测试中,您可以使用自己的自定义函数,
型
y0u0uwnf6#
警告:这可能会使可执行文件的大小增加一点,并消耗一点运行时性能。在我看来,如果golang有像宏或函数装饰器这样的功能,这会更好。
如果你想模拟函数而不改变它的API,最简单的方法是稍微改变一下实现:
字符串
通过这种方式,我们实际上可以从其他函数中模拟出一个函数。为了更方便,我们可以提供这样的模拟样板:
型
在测试文件中:
型
bsxbgnwa7#
我一直在类似的地方.我试图写unitTest的函数,有许多客户端调用它.让我提出2个选项,我探索.其中一个已经在这个线程中讨论,我将无论如何重复它的人搜索的缘故.
方法一:将需要mock的函数声明为全局变量
一种选择是声明全局变量(具有一些凹坑福尔斯)。
例如:
字符串
测试函数如下:
型
注意:测试和实际实现在同一个包中。
以上的方法思想是有限制的!
1.由于竞争条件的风险,不可能运行并行测试。
1.通过使函数成为一个变量,我们引入了一个小的风险,即引用被未来在同一个包中工作的开发人员修改。
方法二:创建 Package 函数
另一种方法是传递沿着你想模仿的方法作为函数的参数来实现可测试性。在我的例子中,我已经有很多客户端调用这个方法,因此,我想避免违反现有的合同。所以,我最终创建了一个 Package 函数。
例如:
型
现在为了测试实际的逻辑,你将测试对
wrappedDownloader
而不是Downloader
的调用,并且你将传递给它一个模拟的getOperation
。这允许你测试所有的业务逻辑,同时不违反你与当前客户端的API合同。brjng4g38#
我是Golang的新手,但我花了几天时间试图找到一种方法来模拟第三方软件包(如
http.Get
)的功能,而不修改源代码。此时,我被迫得出结论,这在Golang中是不可能的,这是一个巨大的失望。dxxyhpgq9#
考虑到单元测试是这个问题的领域,强烈推荐你使用monkey。这个包可以让你在不改变原始源代码的情况下进行模拟测试。与其他答案相比,它更“非侵入性”。
联系我们
字符串
模拟测试
型
坏的一面是:
好的一面是: