2 回答

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

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")
}
}
}
})
}
}
- 2 回答
- 0 關注
- 125 瀏覽
添加回答
舉報