亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

如何優雅地測試 Go 中包含多個客戶端的方法?

如何優雅地測試 Go 中包含多個客戶端的方法?

Go
慕姐8265434 2022-06-13 10:19:39
我有一個Client包含多個客戶端(etcd 和 libvirt)的結構。就像是:type Client struct {  etcd    *clientv3  libvirt *libvirt.Connect}一旦我的圖書館的客戶想要關閉它的句柄,我想關閉這兩個。所以我有:func (c *Client) Close() error {    c.etcd.Close()    c.libvirt.Close()    // Error handling excluded for brevity}什么是測試這個的優雅方法?我目前最好的選擇是創建兩個接口,一個用于兩個包裝客戶端中的每一個。這些接口將包括我的庫使用的兩個客戶端的每一個方法。這應該使得傳遞某種模擬而不是真正的客戶變得相對容易。這可能是前進的方向,但感覺很尷尬。我還有哪些其他選擇?
查看完整描述

2 回答

?
幕布斯6054654

TA貢獻1876條經驗 獲得超7個贊

正如我在評論中提到的,您可以創建一個ClosableClient如下所示的。由于您的每個客戶都有Close方法,因此您可以這樣做。在您的測試文件中,您可以創建只需要實現Close方法的模擬客戶端。您不需要使接口實現所有方法。在您的代碼中,您可以使用類型斷言將其轉換ClosableClient為特定的客戶端以訪問其功能。這是類型斷言的一個很好的例子。


我添加了代碼片段來展示如何使用類型斷言來獲取底層結構。測試文件中的模擬客戶端不需要實現 Foo 和 Bar 方法,因為接口ClosableClient只需要Close方法。


type ClosableClient interface {

    Close()

}


type Etcd struct{}


func (e *Etcd) Close() {

    fmt.Println("etcd closing")

}


func (e *Etcd) Foo() {

    fmt.Println("etcd foo")

}


type Libvirt struct{}


func (l *Libvirt) Close() {

    fmt.Println("libvirt closing")

}


func (l *Libvirt) Bar() {

    fmt.Println("libvirt bar")

}


type Client struct {

    etcd    ClosableClient

    libvirt ClosableClient

}


func (c *Client) Close() {

    c.etcd.Close()

    c.libvirt.Close()

}


func (c *Client) FooBar() {

    etcd, ok := c.etcd.(*Etcd)

    if !ok {

        panic("etcd is of incorrect type")

    }


    etcd.Foo()


    libvirt, ok := c.etcd.(*Libvirt)

    if !ok {

        panic("libvirt is of incorrect type")

    }


    libvirt.Bar()

}


查看完整回答
反對 回復 2022-06-13
?
有只小跳蛙

TA貢獻1824條經驗 獲得超8個贊

受到 poWar 說我的想法很好的評論的鼓舞,我繼續前進:


我更改了我的Client結構以將接口用于我的 libvirt 和 etcd 連接:


type EtcdClient interface {

}


type LibvirtClient interface {

}


type Client struct {

    etcd    EtcdClient

    libvirt LibvirtClient

}

當我嘗試編譯包時,我收到一條類似這樣的錯誤消息:


./main.go:17:18: c.etcd.Close undefined (type EtcdClient is interface with no methods)

./main.go:21:24: c.libvirt.Close undefined (type LibvirtClient is interface with no methods)

不奇怪。然后我在接口中添加了最簡單的 Close() 方法:


type EtcdClient interface {

    Close()

}


type LibvirtClient interface {

    Close()

}

再次編譯給了我:


./main.go:56:10: cannot use etcd (type *clientv3.Client) as type EtcdClient in assignment:

    *clientv3.Client does not implement EtcdClient (wrong type for Close method)

        have Close() error

        want Close()

./main.go:62:13: cannot use lv (type *libvirt.Connect) as type LibvirtClient in assignment:

    *libvirt.Connect does not implement LibvirtClient (wrong type for Close method)

        have Close() (int, error)

        want Close()

然后我用它來填寫接口定義:


type EtcdClient interface {

    Close() error

}


type LibvirtClient interface {

    Close() (int, error)

}

當然,Close這很簡單,我不必經歷這個,但正如我之前提到的,我在這些接口上調用了很多方法,這種方式讓編譯器幫助我填寫接口變得非常簡單定義。


對于測試,我可以制作假貨(模擬?存根?我總是忘記區別)。這是完整的測試文件:


package main


import (

    "errors"

    "testing"

)


type FakeEtcdClient struct {

    wasClosed   bool

    failToClose bool

}


func (f *FakeEtcdClient) Close() error {

    if f.failToClose {

        return errors.New("Fake Etcd failed to Close")

    }

    f.wasClosed = true

    return nil

}


type FakeLibvirtClient struct {

    wasClosed   bool

    failToClose bool

}


func (f *FakeLibvirtClient) Close() (int, error) {

    if f.failToClose {

        return 0, errors.New("Fake libvirt failed to Close")

    }

    f.wasClosed = true

    return 0, nil

}


func TestClient_Close(t *testing.T) {

    type fields struct {

        etcd    EtcdClient

        libvirt LibvirtClient

    }

    tests := []struct {

        name    string

        fields  fields

        wantErr bool

    }{

        {"Happy path", fields{&FakeEtcdClient{}, &FakeLibvirtClient{}}, false},

        {"Etcd fails", fields{&FakeEtcdClient{failToClose: true}, &FakeLibvirtClient{}}, true},

        {"Libvirt fails", fields{&FakeEtcdClient{}, &FakeLibvirtClient{failToClose: true}}, true},

    }

    for _, tt := range tests {

        t.Run(tt.name, func(t *testing.T) {

            c := &Client{

                etcd:    tt.fields.etcd,

                libvirt: tt.fields.libvirt,

            }

            if err := c.Close(); (err != nil) != tt.wantErr {

                t.Errorf("Client.Close() error = %v, wantErr %v", err, tt.wantErr)

            } else {

                if !tt.wantErr {

                    // We only check if the clients have been closed if

                    // Client.Close() returns successfully.

                    if !c.etcd.(*FakeEtcdClient).wasClosed {

                        t.Error("Etcd connection was not closed")

                    }

                    if !c.libvirt.(*FakeLibvirtClient).wasClosed {

                        t.Error("Libvirt connection was not closed")

                    }

                }

            }

        })

    }

}



查看完整回答
反對 回復 2022-06-13
  • 2 回答
  • 0 關注
  • 125 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號