duanjiong1952 2017-05-01 18:23
浏览 34
已采纳

在Golang中测试/模拟第3方软件包

I'm new to Golang and have been taking a TDD approach while learning the language. I've been getting along okay yet I find testing third party packages quite clunky, which leads me to believe that I've been taking the wrong approach.

The specific case I'm having trouble with is mocking a Redis client for error handling. The approach I've taken is to create my own interface, and the implementation wraps the clients methods that I want to use.

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()
}

I can then create a mock which implements that same interface to return whichever values I specify, particularly for testing error handling.

I've hit a roadblock where a specific method on the client to perform transactions (MULTI) returns another interface belonging to that package. What would I do in this scenario? Implementing that interface myself seems out of the question.

Similarly, as usage of this client grows, my own implementation can grow to the point that it implements the whole interface to Redis - this seems to go against the whole idea of delegating this out to an external dependency.

Is there a better way to test third-party packages like this, for things such as error handling?

  • 写回答

1条回答 默认 最新

  • dongzhong9055 2017-05-01 19:59
    关注

    One approach would be to create a type that focuses on what you want to accomplish instead on what methods of the client you are using.

    Let's say all you want is a storage to save and fetch users, you could imagine an interface like this:

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

    You could then implement a Redis version of this storage and call whatever client methods you want inside, it doesn't matter. You can even implement one in PostgreSQL or whatever. Also, mocking is way easier with this approach since you all you need to do is to implement this interface instead of the Redis one.

    Here is an example of a mock version of this interface:

    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
    }
    ...
    

    You can then use this mock in tests like this:

    var m UserStoreMock
    
    m.SaveUserFn = func(u *User) error {
      if u.ID != "123" {
        t.Fail("Bad ID")
      }
    
      return ErrDuplicateError
    }
    ...
    
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 #MATLAB仿真#车辆换道路径规划
  • ¥15 java 操作 elasticsearch 8.1 实现 索引的重建
  • ¥15 数据可视化Python
  • ¥15 要给毕业设计添加扫码登录的功能!!有偿
  • ¥15 kafka 分区副本增加会导致消息丢失或者不可用吗?
  • ¥15 微信公众号自制会员卡没有收款渠道啊
  • ¥100 Jenkins自动化部署—悬赏100元
  • ¥15 关于#python#的问题:求帮写python代码
  • ¥20 MATLAB画图图形出现上下震荡的线条
  • ¥15 关于#windows#的问题:怎么用WIN 11系统的电脑 克隆WIN NT3.51-4.0系统的硬盘