我有一个包装了几个客户端(etcd和libvirt)的Client结构。类似于:
type Client struct {
etcd *clientv3
libvirt *libvirt.Connect
}一旦我的库中的一个客户端想要关闭它的句柄,我就想同时关闭这两个句柄。所以我有:
func (c *Client) Close() error {
c.etcd.Close()
c.libvirt.Close()
// Error handling excluded for brevity
}什么是测试这一点的优雅方法?我目前最好的选择是创建两个接口,每个接口分别用于两个包装的客户端。这些接口将包括我的库使用的两个客户端的每个方法。这应该使得传递某种模拟而不是真正的客户端变得相对容易。这可能是前进的方向,但感觉很尴尬。
我的其他选择是什么?
发布于 2020-06-13 22:17:34
受到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非常简单,我不必经历这些,但正如我前面提到的,我在这些接口上调用了许多方法,这种方式使得让编译器帮助我填充接口定义变得非常简单。
对于测试,我可以做假的(mock?存根?我总是忘记区别)。下面是完整的测试文件:
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")
}
}
}
})
}
}发布于 2020-06-12 22:37:53
正如我在注释中提到的,您可以创建一个ClosableClient,如下所示。因为您的每个客户端都有Close方法,所以您可以这样做。在您的测试文件中,您可以创建只需实现Close方法的模拟客户端。你不需要让接口实现所有的方法。在您的代码中,您可以使用类型断言将ClosableClient转换为特定的客户端以访问其函数。Here是类型断言的一个很好的例子。
我已经添加了代码片段来展示如何使用类型断言来获取底层结构。测试文件中的模拟客户机不需要实现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()
}https://stackoverflow.com/questions/62346168
复制相似问题