Golang 猴子补丁
Golang monkey patching
我知道如果 go 代码的结构使其被编程为接口,那么模拟就很简单了;但是,我正在使用一个我无法更改的代码库(不是我的),但事实并非如此。
这个代码库是高度互连的,没有任何东西被编程到接口,只有结构,所以没有依赖注入。
结构本身只包含其他结构,所以我也不能那样模拟。我不相信我可以对方法做任何事情,并且存在的少数函数不是变量,所以我不知道如何将它们换掉。继承在 golang 中不是一回事,所以这也是不行的。
在像 python 这样的脚本语言中,我们可以在运行时修改对象,也就是猴子补丁。我可以在 golang 中做一些类似的事情吗?试图在不触及底层代码的情况下找出一些方法 test/benchmark。
当我 运行 遇到这种情况时,我的方法是使用我自己的接口作为包装器,允许在测试中进行模拟。例如。
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()
}
如果 Concrete 也是一个接口,您现在可以像模拟 somepackage.Client
一样模拟 Concrete。
正如 @elithrar 在下面的评论中指出的那样,您可以嵌入要模拟的类型,这样您就只能被迫添加需要模拟的方法。例如:
type Concrete struct {
*somepackage.Client
}
像那样完成后,可以直接在 Concrete
上调用 DoSomethingNotNeedingMocking
等其他方法,而无需将其添加到界面/模拟出来。
Go 有一个 available monkey patching library。它仅适用于 Intel/AMD 系统(特别针对 OSX 和 Ubuntu)。
根据情况,您可以应用 "Dependency Inversion Principle" 并利用 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")
}
我知道如果 go 代码的结构使其被编程为接口,那么模拟就很简单了;但是,我正在使用一个我无法更改的代码库(不是我的),但事实并非如此。
这个代码库是高度互连的,没有任何东西被编程到接口,只有结构,所以没有依赖注入。
结构本身只包含其他结构,所以我也不能那样模拟。我不相信我可以对方法做任何事情,并且存在的少数函数不是变量,所以我不知道如何将它们换掉。继承在 golang 中不是一回事,所以这也是不行的。
在像 python 这样的脚本语言中,我们可以在运行时修改对象,也就是猴子补丁。我可以在 golang 中做一些类似的事情吗?试图在不触及底层代码的情况下找出一些方法 test/benchmark。
当我 运行 遇到这种情况时,我的方法是使用我自己的接口作为包装器,允许在测试中进行模拟。例如。
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()
}
如果 Concrete 也是一个接口,您现在可以像模拟 somepackage.Client
一样模拟 Concrete。
正如 @elithrar 在下面的评论中指出的那样,您可以嵌入要模拟的类型,这样您就只能被迫添加需要模拟的方法。例如:
type Concrete struct {
*somepackage.Client
}
像那样完成后,可以直接在 Concrete
上调用 DoSomethingNotNeedingMocking
等其他方法,而无需将其添加到界面/模拟出来。
Go 有一个 available monkey patching library。它仅适用于 Intel/AMD 系统(特别针对 OSX 和 Ubuntu)。
根据情况,您可以应用 "Dependency Inversion Principle" 并利用 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")
}