【问题标题】:How to unit test a component that uses third party library in golang?如何在 golang 中对使用第三方库的组件进行单元测试?
【发布时间】:2021-11-19 06:46:51
【问题描述】:

我是 golang 新手,来自 java 背景。

这是我今天的问题:如何对使用不提供 Golang 接口的第三方库的组件进行单元测试?这是我的具体例子:

我有一个类使用 golang mongodb 驱动来实现一些 DB 操作,如下所示:

package mypackage


type myClientBeingTested struct {
    client *mongo.Client
}

func (mc *myClientBeingTested) FindOne(filter interface{}) (*mongo.SingleResult, error) {
    result := mc.client.FindOne(context.Background(), filter)
    if result.Err() == mongo.ErrNoDocuments {
        return nil, nil
    } else {
        return nil, Errors.New("My own error message")
    }
    return result, nil
}

现在我想为这个方法编写一些单元测试,并意识到不可能模拟没有接口实现的第三方依赖项。在上面的示例中,mongo.Clientstruct 类型。经过一番研究和思考,唯一可能的方法似乎如下:

package mypackage

type myClientBeingTested struct {
    client *mongo.Client
}

var findOneFunc = func(client *mongo.Client, ctx context.Context, filter interface{}) (*mongo.SingleResult, error) {
    return client.findOne(ctx, filter)
}

func (mc *myClientBeingTested) FindOne(filter interface{}) (*mongo.SingleResult, error) {
    result := findOneFunc(mc.client, filter)
    if result.Err() == mongo.ErrNoDocuments {
        return nil, nil
    } else {
        return nil, Errors.New("My own error message")
    }
    return result, nil
}

然后在我的单元测试中,我可以使用我自己的存根来存根findOneFunc,如下所示

findOneFunc = func(client *mongo.Client, ctx context.Context, filter interface{}) (*mongo.SingleResult, error) {
// my own implementation
}

但这似乎是一个 hack。是否有任何真实/推荐的方式来处理这种情况?感谢您的回复!

【问题讨论】:

  • “意识到不可能模拟没有接口实现的第三方依赖”,这在 Java 中是正确的,而不是在 Go 中。接口是隐式实现的,因此不需要在实现中定义它们。看看Tour of Go,特别是关于接口的部分。

标签: unit-testing go


【解决方案1】:

应该可以为需要使用从第 3 方库导入的结构中的方法编写自己的接口。

type MongoClient interface {
  FindOne(context.Context, mongo.D) (*mongo.SingleResult, error)
}

type myClientBeingTested struct {
  client MongoClient
}

// in tests


type mockMongoClient struct {
  // implement MongoClient, pass in to myClientBeingTested
}

但是,对于大多数应用程序,它提供了更好的保证,可以针对本地或内存数据库运行测试,以验证一切是否端到端工作。如果这变得太慢,那么在业务逻辑级别而不是数据库查询级别进行模拟是有意义的。

例如:

type User struct {}

type UserMgmt interface {
  Login(email, pass string) (*User, error)
}

// for testing api or workflows
type MockUserMgmt struct {}

// for production app
type LiveUserMgmt struct {
  client *mongo.Client
}

在单元测试中它看起来像:

// user_mgmt_test.go test code

userMgmt := &LiveUserMgmt{client: mongo.Connect("localhost")}
// test public library methods

在 api 或工作流测试中,它看起来像:

userMgmt := &MockUserMgmt{}

// example pass to api routes
api := &RequestHandler{
  UserMgmt: userMgmt,
}

编辑: 我太新了,无法评论我的帖子,但是关于模拟结构的问题,你应用了相同的原则。如果 mongo 类型是结构体,则可以创建一个接口(即使同名)并依赖于接口而不是直接依赖于结构体。然后通过接口你可以模拟出你需要的方法。

// The mongo struct you depend on and need to mock
type mongo struct {
  someState string
}

// The real world function you need to mock out
func (m *mongo) Foo() error {
  // do stuff
  return nil
}

// Construct an interface with a method that matches the signature you need to mock
type mockableMongoInterface interface {
  Foo() error
}

现在依赖于 mockableMongoInterface 而不是直接依赖于 mongo。您仍然可以将您的第三方 mongo 结构传递给您需要它的站点,因为 go 将通过接口了解类型。

这与 Adrian 对您的问题的评论一致。

【讨论】:

  • 感谢您的回复。感谢。我同意使用这种方法可以让我对组件进行单元测试,但是,实际实现接口并使用mongo.Client 的结构呢?我们最终需要针对实际使用mongo 的组件编写单元测试。例如,对于执行实际数据库连接的组件mongo.connect(ctx, options.Client().ApplyURI("mongdb://uri),如果我想断言传递的optionsURI 值为mongodb://uri
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-06
  • 1970-01-01
  • 2019-05-08
  • 2021-12-07
相关资源
最近更新 更多