【问题标题】:Golang monkey patchingGolang猴子补丁
【发布时间】:2016-09-01 22:20:55
【问题描述】:

我知道,如果 go 代码的结构使得它被编程为接口,那么模拟是微不足道的;但是,我正在使用无法更改的代码库(这不是我的),但事实并非如此。

这个代码库是高度互连的,没有任何东西被编程到接口,只有结构,所以没有依赖注入。

结构本身只包含其他结构,所以我也不能这样模拟。我不相信我可以对方法做任何事情,而且存在的少数函数不是变量,所以我不知道将它们换掉。继承在 golang 中不是一回事,所以这也是不行的。

在像 python 这样的脚本语言中,我们可以在运行时修改对象,也就是猴子补丁。我可以在 golang 中做一些类似的事情吗?试图在不触及底层代码的情况下找出一些测试/基准测试的方法。

【问题讨论】:

    标签: go mocking monkeypatching


    【解决方案1】:

    当我遇到这种情况时,我的方法是使用我自己的接口作为允许在测试中进行模拟的包装器。例如。

    type MyInterface interface {
        DoSomething(i int) error
        DoSomethingElse() ([]int, error)
    }
    
    type Concrete struct {
        client *somepackage.Client
    }
    
    func (c *Concrete) DoSomething(i int) error {
        return c.client.DoSomething(i)
    }
    
    func (c *Concrete) DoSomethingElse() ([]int, error) {
        return c.client.DoSomethingElse()
    }
    

    现在您可以像模拟 somepackage.Client 一样模拟 Concrete(如果它也是一个接口)。

    正如@elithrar 在下面的 cmets 中指出的那样,您可以嵌入您想要模拟的类型,因此您只需要添加需要模拟的方法。例如:

    type Concrete struct {
        *somepackage.Client
    }
    

    这样完成后,可以直接在 Concrete 上调用像 DoSomethingNotNeedingMocking 这样的附加方法,而无需将其添加到接口/模拟出来。

    【讨论】:

    • 我想确保我理解正确。您建议定义一个包装我正在处理的任何结构的结构。在所述新引入的结构上定义调用包装结构的方法的方法。在包装器结构上定义一个接口,然后选择性地模拟出需要模拟出的任何底层逻辑子集?如果我的解释是正确的,这看起来就像我只是重写所有代码,因为它是如此交织在一起。也许这是意料之中的——我猜可能没有其他选择。
    • 这可能是不幸的,这正是我的建议。
    • 你不需要调用/模拟你不需要重写的方法。您将原始类型嵌入到您的类型中,这会提升方法,然后只覆盖您想要的方法:play.golang.org/p/oHW1JX8iFb
    • @elithrar 不错的主意。通常,我最终使我的接口函数比直接传递更复杂(就像我在这个例子中所做的那样),但你对嵌入式类型确实是正确的。
    • @elithrar 想法的问题在于,被覆盖的方法在另一个方法中使用时不会被调用,但原来的方法会被调用。
    【解决方案2】:

    Go 有一个 available monkey patching library。它仅适用于 Intel/AMD 系统(特别针对 OSX 和 Ubuntu)。

    【讨论】:

      【解决方案3】:

      根据情况,您可以应用“依赖倒置原则”并利用 Go 的隐式接口。

      为此,您可以在包中定义您的需求接口以及使用情况(而不是定义您在实现它的包中提供的内容;就像在 Java 中一样)。

      然后你就可以独立于依赖来测试你的代码了。

      具有结构依赖的典型对象:

      // Object that relies on a struct
      type ObjectUnderTestBefore struct {
          db *sql.DB
      }
      
      func (o *ObjectUnderTestBefore) Delete() error {
          o.db.Exec("DELETE FROM sometable")
      }
      

      应用依赖倒置原则(带隐式接口)

      // subset of sql.DB which defines our "requirements"
      type dbExec interface {
          Exec(query string, args ...interface{}) (sql.Result, error)
      }
      
      // Same object with it's requirement defined as an local interface
      type ObjectUnderTestWithDIP struct {
          // *sql.DB will implicitly implement this interface
          db dbExec
      }
      
      func (o *ObjectUnderTestWithDIP) Delete() error {
          o.db.Exec("DELETE FROM sometable")
      }
      

      【讨论】:

        猜你喜欢
        • 2012-09-16
        • 2012-12-18
        • 2020-01-09
        • 1970-01-01
        • 1970-01-01
        • 2010-09-29
        • 2021-04-04
        • 2011-04-15
        • 2017-05-20
        相关资源
        最近更新 更多