【问题标题】:Mocking non-interface types in Go在 Go 中模拟非接口类型
【发布时间】:2017-07-30 18:22:42
【问题描述】:

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

在使用第 3 方客户端库(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

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

这甚至是一个可模拟的场景吗?是否有特殊技术可以解决库不提供接口作为返回类型这一事实?

【问题讨论】:

  • UnderlyingType 可以做什么?它是否具有导出的字段或方法?或者它只是你传递给包中​​其他函数的东西?如果secret 在包内部,您似乎不需要模拟它进行测试。
  • 在我的例子中,UnderlyingType 是 io.Reader 的一个实现。 UnderlyingType 的“秘密”是对客户端的引用。由于我无法为模拟客户端实例分配“秘密”,因此我无法对 UnderlyingType 的客户端引用执行模拟操作或断言。
  • 回答您关于导出方法的问题,是的。导出的方法尝试引用私有成员。由于我的模拟客户端是由实际实现组成的,因此模拟的导出函数最终将引用 nil,因为我无法设置私有成员。
  • 返回UnderlyintType 的“模拟”不能解决问题吗?如果它是 io.Reader,并且它是您的应用运行所需的全部,那么您可能想要返回 io.Reader 而不是 UnderlyingType
  • 如果您正在使用this,那么您可能需要考虑在其之上构建一个抽象并将storage.Client 视为实现细节,有点像您将在顶部构建存储库Go 的database/sql 包... 例如如果你想要的只是存储和检索文件,那么创建你自己的类型Store 来处理io.Readers/io.Writers 并且其实现使用谷歌存储,但是在你的测试中你可以模拟你的@ 987654335@ 类型而不是整个 cloud.google.com/go/storage 包。

标签: unit-testing go mocking


【解决方案1】:

通常,在处理非测试友好的第三方库时,您可以采取的一种方法是使用中间层抽象第三方代码。

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

type intermediateImplementation struct{}

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

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

然后,问题将转移到测试IntermediateLayer。根据使用第三方库的代码的复杂程度,您可以选择不对其进行测试,也可以将其留给更高级别的测试(如集成测试)进行验证。

走这条路的一个好处是,您可以将业务代码与第三方库分离,这样您就可以在将来的某个时候切换到不同的第三方库,而无需重新编写所有代码。即使在处理对测试友好的第三方库时,您甚至可以考虑使用这种方法,但代价是更多的抽象和样板代码。

【讨论】:

    【解决方案2】:

    您的问题的答案是:是的,您可以这样做。

    但是你问错了问题。你不应该问你怎么能嘲笑一些东西。因为,什么时候需要模拟?

    仅用于测试。所以你应该做一个你想要测试的具体例子。

    当您使用外部包时,您有两种可能性。您想测试外部包的行为是否符合您的预期,或者您信任该外部包并且您只是在测试您的代码。

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

    【讨论】:

    • 在他的情况下,他想编写一个单元测试(=测试他的代码),但由于库而不允许。在这种情况下我们如何进行?我们围绕它包装一个接口?
    • 我明白了。但问题不在于嘲笑,而在于测试。当你测试时,你应该假设第三个库工作正常。你想测试的函数是什么样子的?
    • 我刚刚在这里问了你是否想关注 :) stackoverflow.com/questions/55105509/golang-interfaces-mocking
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-04-05
    • 2022-01-08
    • 2019-05-10
    • 2013-06-10
    • 1970-01-01
    • 2010-12-30
    • 2021-02-10
    相关资源
    最近更新 更多