Go中的模拟function

我正在通过编写一个小型的个人项目来学习Go。 尽pipe它很小,但我决定从一开始就进行严格的unit testing,以在Go上学习良好的习惯。

琐碎的unit testing都很好,但是我现在对依赖关系感到困惑。 我想能够用模拟的replace一些函数调用。 这是我的代码片段:

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]) } } 

我希望能够testingdownloader(),而实际上没有通过http获取页面 – 即通过模拟get_page(更容易,因为它只返回页面内容作为string)或http.Get()。

我发现这个线程: https : //groups.google.com/forum/#!topic/golang- nuts/ 6AN1E2CJOxI这似乎是一个类似的问题。 Julian Phillips将他的库Withmock( http://github.com/qur/withmock )作为一个解决scheme,但是我无法使其工作。 以下是我的testing代码的相关部分,这对我来说主要是货真价实的代码,说实话:

 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 } 

testing结果如下:

 ERROR: Failed to install '_et/http': exit status 1 output: can't load package: package _et/http: found packages http (chunked.go) and main (main_mock.go) in /var/folders/z9/ql_yn5h550s6shtb9c5sggj40000gn/T/withmock570825607/path/src/_et/http 

Withmock是我的testing问题的解决scheme吗? 我该怎么做才能使其工作?

荣誉给你练习好的testing! 🙂

就个人而言,我不使用gomock (或任何嘲笑的框架,嘲笑Go是非常容易的没有它)。 我要么将依赖关系作为parameter passing给downloader()函数,要么将downloader()作为types的方法,而types可以保持get_page依赖关系:

方法1:传递get_page()作为downloader()的参数

 type PageGetter func(url string) string func downloader(pageGetterFunc PageGetter) { // ... content := pageGetterFunc(BASE_URL) // ... } 

主要:

 func get_page(url string) string { /* ... */ } func main() { downloader(get_page) } 

testing:

 func mock_get_page(url string) string { // mock your 'get_page()' function here } func TestDownloader(t *testing.T) { downloader(mock_get_page) } 

方法2:使download()一个types的Downloader的方法:

如果你不想传递依赖作为参数,你也可以使get_page()成为一个types的成员,并使download()成为该types的方法,然后可以使用get_page

 type PageGetter func(url string) string type Downloader struct { get_page PageGetter } func NewDownloader(pg PageGetter) *Downloader { return &Downloader{get_page: pg} } func (d *Downloader) download() { //... content := d.get_page(BASE_URL) //... } 

主要:

 func get_page(url string) string { /* ... */ } func main() { d := NewDownloader(get_page) d.download() } 

testing:

 func mock_get_page(url string) string { // mock your 'get_page()' function here } func TestDownloader() { d := NewDownloader(mock_get_page) d.download() } 

我会做一些像,

主要

 var getPage = get_page func get_page (... func downloader() { dl_slots = make(chan bool, DL_SLOT_AMOUNT) // Init the download slot semaphore content := getPage(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]) } } 

testing

 func TestDownloader (t *testing.T) { origGetPage := getPage getPage = mock_get_page defer func() {getPage = origGatePage}() // The rest to be written } // define mock_get_page and rest of the codes func mock_get_page (.... 

我会在golang中避免_ 更好地使用camelCase

如果你改变你的函数定义来使用一个variables:

 var get_page = func(url string) string { ... } 

您可以在testing中覆盖它:

 func TestDownloader(t *testing.T) { get_page = func(url string) string { if url != "expected" { t.Fatal("good message") } return "something" } downloader() } 

小心,但是如果你testing了你重写的函数的function,你的其他testing可能会失败!

Go作者在Go标准库中使用这个模式来将testing钩子插入到代码中以使testing更容易:

https://golang.org/src/net/hook.go

https://golang.org/src/net/dial.go#L248

https://golang.org/src/net/dial_test.go#L701