GOLANG寬泛介面在測試中的大用處

winlin發表於2017-06-23

考慮測試一個函式:

func request(ctx context.Context, hc *http.Client, api string) (err error) {
    var hreq *http.Request
    if hreq, err = http.NewRequest("GET", api, nil); err != nil {
        return nil, errors.Wrap(err, "create request")
    }
    var hres *http.Response
    if hres, err = hc.Do(hreq.WithContext(ctx)); err != nil {
        return nil, errors.Wrap(err, "do request")
    }
    defer hres.Body.Close()

    var body []byte
    if body, err = ioutil.ReadAll(hres.Body); err != nil {
        return nil, errors.Wrap(err, "read body")
    }

    // ......
    return nil
}

這個函式的引數是一個*http.Client,而不是介面,這個該如何測試?內嵌一個http.Client像這樣嗎?

type mockHttpClient struct {
    http.Client
}

但是,問題是這樣總是很噁心不是嗎?就像如果是C++中,我們只能寫一個mock類從要測試的類繼承,但是我們只需要重寫Do這個方法啊。

注意:對於C++而言,這是為何要求建構函式只是初始化,而不能包含邏輯,想象一個類在建構函式就訪問了資料庫,請問如何MOCK它?是做不到的,因此只能在建構函式初始化資料庫的IP和賬號等資訊,提供connect這種函式連線資料庫。

備註:上面只是拿資料庫連線打個比方,實際上從MOCK角度來說,建構函式只能初始化記憶體物件,其他的應該啥也不幹。

在GOLANG中,有個非常牛逼的方法,就是建立一個私有的介面,使用時用介面:

type httpDoer interface {
    Do(req *http.Request) (*http.Response, error)
}
func request(ctx context.Context, hc httpDoer, api string) (err error) {
    // ......

可以發現,很神奇的是,呼叫者也可以給*http.Client,對這個改動一無所知,這難道不是極其巧妙的設計嗎?我們在mock中只需要mock這個方法就可以了。

一行程式碼處,深藏功與名~

相關文章