猿问

如何优雅地测试 Go 中包含多个客户端的方法?

我有一个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

}

什么是测试这个的优雅方法?我目前最好的选择是创建两个接口,一个用于两个包装客户端中的每一个。这些接口将包括我的库使用的两个客户端的每一个方法。这应该使得传递某种模拟而不是真正的客户变得相对容易。这可能是前进的方向,但感觉很尴尬。


我还有哪些其他选择?


慕姐8265434
浏览 104回答 2
2回答

幕布斯6054654

正如我在评论中提到的,您可以创建一个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()}

有只小跳蛙

受到 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 mainimport (    "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")                    }                }            }        })    }}
随时随地看视频慕课网APP

相关分类

Go
我要回答