如何在Golang中对使用第三方库的组件进行单元测试?

huangapple go评论78阅读模式
英文:

How to unit test a component that uses third party library in golang?

问题

我是你的中文翻译助手,以下是翻译好的内容:

我是一个从Java背景转到Golang的新手。

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

我有一个类,使用Golang的mongodb驱动程序来实现一些数据库操作,代码如下:

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.Client是一个struct类型。经过一些研究和思考,唯一可能的方法似乎是像下面这样:

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) {
    // 我自己的实现
}

但是这似乎是一种破解的方法。是否有任何正宗/推荐的方法来处理这种情况?感谢您的回答!

英文:

I am new to golang and came from a java background.

Here is my problem today: How to unit test a component that uses a third-party library that doesn't provide an interface in Golang? Here is my concrete example:

I have a class that uses golang mongodb driver to implement some DB operations like below:

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
}

Now I'd like to write some unit tests for this method and realized that it's impossible to mock a third party dependency that doesn't have an interface implementation. In the example above, mongo.Client is a struct type. After some researching and thinking, the only possible way seems to be like below:

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
}

Then in my unit test I can stub findOneFunc with my own stub like below

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

But this seems to be a hack. Is there any authentic/recommended way to handling situations like that? Appreciate your responses!

答案1

得分: 1

应该可以编写自己的接口,用于从第三方库导入的结构中使用所需的方法。

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

type myClientBeingTested struct {
  client MongoClient
}

// 在测试中

type mockMongoClient struct {
  // 实现MongoClient,传递给myClientBeingTested
}

然而,对于大多数应用程序来说,通过运行针对本地或内存数据库的测试来验证一切是否正常运行会提供更好的保证。如果这变得太慢,可以考虑在业务逻辑层面而不是数据库查询层面进行模拟。

例如:

type User struct {}

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

// 用于测试API或工作流程
type MockUserMgmt struct {}

// 用于生产应用程序
type LiveUserMgmt struct {
  client *mongo.Client
}

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

// user_mgmt_test.go 测试代码

userMgmt := &LiveUserMgmt{client: mongo.Connect("localhost")}
// 测试公共库方法

在API或工作流程测试中,它看起来像这样:

userMgmt := &MockUserMgmt{}

// 例如传递给API路由
api := &RequestHandler{
  UserMgmt: userMgmt,
}

编辑:
我对我的帖子没有评论权限,但是关于您关于模拟结构的问题,您可以应用相同的原则。如果mongo类型是一个结构体,您可以创建一个接口(甚至使用相同的名称)并依赖于接口而不是直接依赖于结构体。然后,通过接口,您可以模拟所需的方法。

// 您依赖并需要模拟的mongo结构
type mongo struct {
  someState string
}

// 您需要模拟的真实世界函数
func (m *mongo) Foo() error {
  // 做一些事情
  return nil
}

// 构建一个具有与您需要模拟的签名匹配的方法的接口
type mockableMongoInterface interface {
  Foo() error
}

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

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

英文:

It should be possible to write your own interface for the methods that you need to use from a struct imported from a 3rd party library.

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
}

However for most apps it provides a better guarantee to run tests against a local or in memory database to verify that everything works end to end. If that becomes too slow it can make sense to mock at the business logic level instead of the database query level.

For example:

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
}

In the unit test it would look like:

// user_mgmt_test.go test code

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

In api or workflow tests it would look like:

userMgmt := &MockUserMgmt{}

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

EDIT:
I'm too new to comment on my post, but re your question about mocking the struct, you apply the same principle. If the mongo type is a struct, you can create an interface (even with the same name) and depend on the interface instead of directly depending on the struct. Then via the interface you can mock out the methods you need to.

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

Now depend on mockableMongoInterface instead of directly on mongo. You can still pass your third party mongo struct to sites where you need it, because go will understand the type via the interface.

This aligns with Adrian's comment on your question.

huangapple
  • 本文由 发表于 2021年9月27日 12:20:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/69341177.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定