繁体   English   中英

使用指针接收器方法创建包装现有类型的接口

[英]Create interface wrapping existing types with pointer-receiver methods

我需要测试一个使用 Google Cloud Pubsub 的应用程序,因此必须包装其类型pubsub.Clientpubsub.Subscriber以进行测试。 但是,尽管进行了多次尝试,但我还是无法在它们周围找到一个可以编译的接口。

我试图包装的方法的定义是:

func (s *Subscription) Receive(
    ctx context.Context, f func(context.Context, *Message)) error

func (c *Client) Subscription(id string) *Subscription

这是当前的代码。 Receiver接口(环绕Subscriber包装器)似乎可以工作,但我怀疑它可能需要更改才能修复SubscriptionMaker ,所以我已经包含了两者。

注意:我已经尝试了几种引用和取消引用指针的变体,所以请不要告诉我改变它,除非你解释了为什么你的建议配置是正确的,或者你已经亲自验证了它编译。

import (
    "context"

    "cloud.google.com/go/pubsub"
)

type Receiver interface {
    Receive(context.Context, func(ctx context.Context, msg *pubsub.Message)) (err error)
}

// Pubsub subscriptions implement Receiver
var _ Receiver = &pubsub.Subscription{}

type SubscriptionMaker interface {
    Subscription(name string) (s Receiver)
}

// Pubsub clients implement SubscriptionMaker
var _ SubscriptionMaker = pubsub.Client{}

当前错误信息:

common_types.go:21:5: cannot use "cloud.google.com/go/pubsub".Client literal (type "cloud.google.com/go/pubsub".Client) as type SubscriptionMaker in assignment:
    "cloud.google.com/go/pubsub".Client does not implement SubscriptionMaker (wrong type for Subscription method)
        have Subscription(string) *"cloud.google.com/go/pubsub".Subscription
        want Subscription(string) Receiver

首先,对于大多数用途,使用ptest包可能是一种更容易测试 pubsub 的方法。 但是,当然,您的具体问题可以适用于任何库,下面的方法对很多事情pubsub ,而不仅仅是模拟pubsub


您使用接口来模拟这样的库的更广泛目标是可行的。 但是当您希望模拟的库返回您无法模拟的具体类型(可能是由于未报告的字段)时,情况就很复杂了。 要采用的方法比通常值得采用的方法要复杂得多,因为可能有更简单的方法来测试您的代码。

但是如果您打算这样做,您必须采取的方法是不要将整个包包装在接口中,而不仅仅是您希望模拟的特定方法。

您还需要包装您希望模拟的任何类型,这些类型也由您的接口返回或接受。 这通常意味着您还需要修改您的生产代码(不仅仅是您的测试代码),因此这有时可能会破坏现有代码库。

我以前通常在模拟标准库的 sql 驱动程序之类的东西时这样做,但这里可以应用相同的方法。 本质上,您需要为您的pubsub库创建一个包装程序包,您甚至可以在生产代码中使用它。 同样,这对现有的代码库来说可能非常具有侵入性,但为了说明起见。 使用您定义的接口:

package mypubsub

import "cloud.google.com/go/pubsub"

type Receiver interface {
    Recieve(context.Context, func(context.Context, *pubsub.Message) error)
}

type SubscriptionMaker interface {
    Subscription(string) Receiver
}

然后,您可以包装默认实现,以用于生产代码:

// defaultClient wraps the default pubsub Client functionality.
type defaultClient struct {
    *pubsub.Client
}

func (d defaultImplementation) Subscription(name string) Receiver {
    return d.Client.Subscription()
}

自然地,您需要扩展此包以包装您正在使用的大部分或全部pubsub包。 这可能有点令人生畏。

但是一旦你这样做了,然后在你的代码中到处使用你的mypubsub包,而不是直接依赖于pubsub包。 现在,您可以在任何需要测试的地方轻松更换模拟实现。

做不到。

在接口上定义方法的类型签名时,它必须完全匹配。 func (c *Client) Subscription(id string) *Subscription返回一个*Subscription ,并且一个*Subscription是一个有效的Receiver ,但它不符合接口方法Subscription(string) Receiver Go 需要对函数签名进行精确匹配,而不是它通常用于接口的鸭子类型样式。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM