【问题标题】:Testing/mocking 3rd party packages in Golang在 Golang 中测试/模拟 3rd 方包
【发布时间】:2017-09-29 04:09:14
【问题描述】:

我是 Golang 的新手,在学习该语言时一直采用 TDD 方法。我一直相处得很好,但我发现测试第三方软件包非常笨拙,这让我相信我采取了错误的方法。

我遇到问题的具体情况是模拟 Redis client 以进行错误处理。我采取的方法是创建自己的接口,实现包装我要使用的客户端方法。

type Redis interface {
    Get(key string) (string, error)
}

type RedisClient struct {
    client *redis.Client
}

func (redisClient *RedisClient) New(client *redis.Client) *RedisClient {
    redisClient.client = client

    return redisClient
}

func (redisClient *RedisClient) Get(key string) (string, error) {
    return redisClient.client.Get(key).Result()
}

然后我可以创建一个模拟来实现相同的接口以返回我指定的任何值,特别是用于测试错误处理。

我遇到了一个障碍,客户端上的特定方法 perform transactions (MULTI) 返回属于该包的另一个接口。在这种情况下我会怎么做?自己实现该接口似乎是不可能的。

同样,随着该客户端使用量的增长,我自己的实现可能会增长到实现 Redis 的整个接口——这似乎违背了将其委托给外部依赖项的整个想法。

有没有更好的方法来测试这样的第三方包,例如错误处理?

【问题讨论】:

  • 为什么对您来说似乎不可能?我认为声明一个由redis.Pipeline 实现的新接口并让您的RedisClient.TxPipeline 方法返回该接口而不是redis.Pipeline 是非常好的。
  • 你最终得出了什么结论?我也有类似的困境。
  • @Lansana 我的方法基本上如下,集成测试涵盖与任何依赖项的交互。对于非 Redis 特定的东西,或者任何无法通过集成测试进行测试的东西,mkopriva 的建议也有效。
  • 愿意分享一个你的 Redis 实现的虚拟示例吗?

标签: testing go tdd


【解决方案1】:

一种方法是创建一种类型,专注于您想要完成的任务,而不是您正在使用的客户端的方法。

假设您只需要一个存储来保存和获取用户,您可以想象这样的界面:

type UserStore interface {
  SaveUser(*User) error
  GetUserByID(id string) (*User, error)
  SearchUsers(query string) ([]User, error)
}

然后您可以实现此存储的 Redis 版本并调用您想要的任何客户端方法,这没关系。您甚至可以在 PostgreSQL 或其他任何地方实现一个。 此外,使用这种方法进行模拟更容易,因为您需要做的就是实现这个接口而不是 Redis 接口。

这里是这个接口的一个模拟版本的例子:

type UserStoreMock struct {
  SaveUserFn func (*User) error
  SaveUserInvoked bool
  ...
}

func (m *UserStoreMock) SaveUser(u *User) error {
  m.SaveUserInvoked = true

  if m.SaveUserFn != nil {
    return m.SaveUserFn(u)
  }

  return nil
}
...

然后你可以在这样的测试中使用这个模拟:

var m UserStoreMock

m.SaveUserFn = func(u *User) error {
  if u.ID != "123" {
    t.Fail("Bad ID")
  }

  return ErrDuplicateError
}
...

【讨论】:

  • UserStore的实现以及它与redis的交互方式最终是我要测试的——比如我想断言如果redis返回一个错误,那么这个错误是正确处理。这就是我试图模拟 redis 客户端的原因。
  • 测试UserStore 的每个实现都需要不使用模拟IMO,而是进行实际调用,因为即使你在模拟你也不能确定你的实现在没有模拟的情况下会以同样的方式工作(想象一下有多难它将与 PostgreSQL 一起使用)。这种方法使您可以灵活地将实际的 Redis 测试与您的其余代码隔离开来,这些代码只能使用模拟 UserStore
  • @cejast Asdine 的代码示例绝对是您想要的方式,因为它是一个简洁的设计。您仍然可以按照他的建议使用模拟等进行测试。如果你想测试你的代码如何与来自 Redis 客户端的错误交互,只需从你的模拟客户端返回一个错误。如果你想测试你的代码是否真的可以与真实的 Redis 实例一起工作,你必须编写一两个集成测试。
猜你喜欢
  • 2015-03-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-01-02
  • 2022-07-28
  • 1970-01-01
  • 2016-08-09
相关资源
最近更新 更多