首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何在Go中优雅地测试包装多个客户端的方法?

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

Stack Overflow用户
提问于 2020-06-12 22:17:34
回答 2查看 169关注 0票数 0

我有一个包装了几个客户端(etcd和libvirt)的Client结构。类似于:

代码语言:javascript
复制
type Client struct {
  etcd    *clientv3
  libvirt *libvirt.Connect
}

一旦我的库中的一个客户端想要关闭它的句柄,我就想同时关闭这两个句柄。所以我有:

代码语言:javascript
复制
func (c *Client) Close() error {
    c.etcd.Close()
    c.libvirt.Close()
    // Error handling excluded for brevity
}

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

我的其他选择是什么?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2020-06-13 22:17:34

受到poWar关于我的想法很好的评论的鼓舞,我继续这样做:

我更改了我的Client结构,为我的libvirt和etcd连接使用接口:

代码语言:javascript
复制
type EtcdClient interface {
}

type LibvirtClient interface {
}

type Client struct {
    etcd    EtcdClient
    libvirt LibvirtClient
}

当我试图编译这个包时,我得到了类似如下的错误消息:

代码语言:javascript
复制
./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()方法添加到接口:

代码语言:javascript
复制
type EtcdClient interface {
    Close()
}

type LibvirtClient interface {
    Close()
}

再一次编译给了我:

代码语言:javascript
复制
./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()

然后我用它来填充接口定义:

代码语言:javascript
复制
type EtcdClient interface {
    Close() error
}

type LibvirtClient interface {
    Close() (int, error)
}

当然,Close非常简单,我不必经历这些,但正如我前面提到的,我在这些接口上调用了许多方法,这种方式使得让编译器帮助我填充接口定义变得非常简单。

对于测试,我可以做假的(mock?存根?我总是忘记区别)。下面是完整的测试文件:

代码语言:javascript
复制
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")
                    }
                }
            }
        })
    }
}
票数 0
EN

Stack Overflow用户

发布于 2020-06-12 22:37:53

正如我在注释中提到的,您可以创建一个ClosableClient,如下所示。因为您的每个客户端都有Close方法,所以您可以这样做。在您的测试文件中,您可以创建只需实现Close方法的模拟客户端。你不需要让接口实现所有的方法。在您的代码中,您可以使用类型断言将ClosableClient转换为特定的客户端以访问其函数。Here是类型断言的一个很好的例子。

我已经添加了代码片段来展示如何使用类型断言来获取底层结构。测试文件中的模拟客户机不需要实现Foo和Bar方法,因为接口ClosableClient只需要Close方法。

代码语言:javascript
复制
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()
}
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/62346168

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档