Mocking non-interface types in Go

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

Mocking non-interface types in Go

问题

让我先说一下,我对Go语言还比较新手,所以我正在寻找在使用其他库时的模拟技术。我很清楚,接口和依赖注入是保持代码可测试和可模拟的最佳方式。

在使用第三方客户端库(Google Cloud Storage)时,我遇到了一个问题,即尝试模拟其客户端实现。主要问题是客户端库中的类型没有使用接口实现。我可以生成接口来模拟客户端实现。然而,某些函数的返回值是指向底层结构类型的指针,由于私有属性的存在,这些指针很难或无法进行模拟。以下是我试图解决的问题示例:

package third_party

type UnderlyingType struct {
    secret string
}

type ThirdPartyClient struct {}
func (f *ThirdPartyClient) SomeFunction() *UnderlyingType {
    return &UnderlyingType{
        secret: "I can't mock this, it's a secret to the package"
    }
}

以下是带有我试图解决的问题注释的示例:

package mock

// 创建与第三方客户端结构匹配的接口
type MyClientInterface interface {
    SomeFunction() *third_party.UnderlyingType
}

type MockClient struct {
    third_party.Client
}
// 强制返回第三方非接口类型'UnderlyingType'
func (f *MockClient) SomeFunction() *UnderlyingType { 

    // 在第三方包之外,无法模拟'secret'属性的值
    // 任何依赖于非nil引用'secret'的底层方法将无法正常工作
    //
    // TODO: 找到一种方法来模拟'secret'值
    return &UnderlyingType{}
}

这种情况是否可模拟?是否有特殊的技术来解决库没有提供接口作为返回类型的问题?

英文:

Let me preface this by saying I'm pretty new to Go, so I am looking for mocking techniques when working with other libraries. I am well aware that interfaces and dependency injection are the best way to keep code testable and mockable.

While working with a 3rd party client library (Google Cloud Storage), I have run into a problem with attempting to mock the implementation of their client. The primary problem is that the types in the client library are not implemented with interfaces. I can generate interfaces to mimic the client implementation. However, the return values for some of the functions return pointers to underlying struct types which are tricky or impossible to mock due to private attributes. Here is a sample of the problem I am trying to solve:

package third_party

type UnderlyingType struct {
    secret string
}

type ThirdPartyClient struct {}
func (f *ThirdPartyClient) SomeFunction() *UnderlyingType {
  	return &UnderlyingType{
  		  secret: "I can't mock this, it's a secret to the package"
  	}
}

Here is an annotated sample with the problem I'm trying to solve.

package mock

// Create interface that matches third party client structure
type MyClientInterface interface {
    SomeFunction() *third_party.UnderlyingType
}

type MockClient struct {
    third_party.Client
}
// Forced to return the third party non-interface type 'UnderlyingType'
func (f *MockClient) SomeFunction() *UnderlyingType { 
	
    // No way to mock the value of the 'secret' property outside
    // of the third-party package. Any underlying methods that 
    // depend on a non-nil reference to 'secret' will explode 
    // with the mock.
    //
    // TODO: Find a way to mock the 'secret' value
    return &UnderlyingType{}
}

Is this even a mockable scenario? Are there special techniques to work around the fact that the library provides no interfaces as return types?

答案1

得分: 12

通常,在处理不友好的第三方库时,你可以采取一种方法,即使用中间层将第三方代码抽象出来。

// 模拟并使用这个接口
type IntermediateLayer interface {
    DoSomething()
}

type intermediateImplementation struct{}

func (i intermediateImplementation) DoSomething() {
    client := &ThirdPartyClient{}
    underlyingValue := client.SomeFunction()
    underlyingValue.SomeOtherFunction()
}

你可以模拟IntermediateLayer接口并测试使用它的业务代码。你需要创建一个实现IntermediateLayer接口的结构体,并使用第三方API来实现你的目标。

然后,问题就转移到了测试IntermediateLayer上。根据使用第三方库的代码有多复杂,你可以选择不测试它,或者将其留给更高级别的测试(如集成测试)来验证。

这种方法的一个好处是,你将业务代码与第三方库解耦,这样你可以在将来的某个时候切换到不同的第三方库,而无需重新编写所有代码。即使在处理测试友好的第三方库时,你甚至可以考虑使用这种方法,尽管会增加更多的抽象和样板代码。

英文:

In general, one approach you can take when dealing with non-test-friendly third party libraries is to abstract the third party code away with an intermediate layer.

// mock and use this interface
type IntermediateLayer interface {
    DoSomething()
}

type intermediateImplementation struct{}

func (i intermediateImplementation) DoSomething() {
    client := &ThirdPartyClient{}
    underlyingValue := client.SomeFunction()
    underlyingValue.SomeOtherFunction()
}

You can mock the IntermediateLayer interface and test the business code that uses it. You will need to create a structure that implements the IntermediateLayer interface and uses the third party API to achieve your goal.

Then, the problem would be shifted to testing the IntermediateLayer. Depending on how complex the code that uses the third party library is, you can either opt to not test it or leave it to higher level tests (like integration tests) to verify it.

One benefit of going down this road is that you are decoupling your business code from the third party library, which allows you to switch to a different third party library at some point in the future without having to rework all of your code. You may even consider using this approach even when dealing with test-friendly third party libraries, at the cost of more abstractions and boilerplate code.

答案2

得分: 2

你的问题的答案是:是的,这是你可以做的方式。

但是你问错了问题。你不应该问如何模拟某个东西。因为,你什么时候需要模拟?

只是为了测试。所以你应该给出一个具体的例子,你想要测试什么。

当你使用外部包时,有两种可能性。你想要测试外部包是否按照你的期望行为,或者你相信外部包,只是测试你的代码。

所以当你测试你的代码时,你需要测试客户端是否被正确调用。所以你的模拟对于这种情况是可以的。只要记住重要的是你正在测试什么,而不是你是否可以模拟某些东西。

英文:

The answer to your question is: yes this is the way you could do it.

But you asked the wrong question. You should not aks how you could mock something. Because, when do you need a mock?

Just for testing. So you should make a specific example what you want to test.

When you use external packages you have 2 possibilities. You want to test, if the external package behaves like you expect, or you trust that external package and you are just testing your code.

So when you test your code you need to test, if the client is called correct. So your mock is ok for that case. Just keep in mind it is important, what you are testing and not if you could mock something.

huangapple
  • 本文由 发表于 2017年3月10日 00:45:07
  • 转载请务必保留本文链接:https://go.coder-hub.com/42700893.html
匿名

发表评论

匿名网友

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

确定