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