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

我需要测试一个使用 Google Cloud Pubsub 的应用程序,因此必须包装其类型pubsub.Client并pubsub.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


智慧大石
浏览 105回答 2
2回答

catspeake

首先,对于大多数用途,使用ptest包可能是测试 pubsub 的更简单的方法。但是当然,您的具体问题可以适用于任何库,并且下面的方法对很多事情都有用,而不仅仅是模拟pubsub。使用接口来模拟这样的库的更广泛目标是可行的。但是,当您希望模拟的库返回您无法模拟的具体类型(可能是由于未报告的字段)时,情况会很复杂。所采取的方法比通常值得的要复杂得多,因为可能有更简单的方法来测试代码。但是,如果您打算这样做,则必须采取的方法是不要将整个包包装在接口中,而不仅仅是您希望模拟的特定方法。您还需要包装您希望模拟的任何类型,这些类型也由您的接口返回或接受。这通常意味着您还需要修改生产代码(而不仅仅是测试代码),因此这有时可能会破坏现有代码库的交易。我以前通常这样做的地方是在模拟标准库的 sql 驱动程序之类的东西时,但这里可以应用相同的方法。本质上,您需要为您的pubsub库创建一个包装器包,甚至可以在生产代码中使用它。同样,这可能会对现有代码库造成很大的干扰,但只是为了说明目的。使用您定义的接口:package mypubsubimport "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包。现在,您可以在需要测试的任何地方轻松更换模拟实现。

大话西游666

这是不可能的。在接口上定义方法的类型签名时,它必须完全匹配。func (c *Client) Subscription(id string) *Subscription返回 a *Subscription,并且 a*Subscription是有效的Receiver,但它不算符合接口方法Subscription(string) Receiver。Go 需要函数签名的精确匹配,而不是通常用于接口的鸭子类型风格。
打开App,查看更多内容
随时随地看视频慕课网APP