我的问题是你如何决定注入依赖项的位置,以及如何测试首次将依赖项注入函数的函数?
例如,我正在重构一些Go代码以使用依赖注入,目标是使代码更易于测试。
这是我重构的代码:
type FooIface interface {
FooFunc()
}
type Foo struct {}
func (f *Foo) FooFunc() {
// Some function I would like to stub
}
func main() {
OuterFunction()
}
func OuterFunction() {
fooVar := &Foo{}
InnerFunction(fooVar)
// Other stuff
}
func InnerFunction(f *FooIface) {
f.FooFunc()
// Other stuff
}
我能够通过创建一个使用存根FooFunc()实现FooIface的模拟结构来轻松测试InnerFunction,所以这里一切都很好。但是,如果我想测试OuterFunction()怎么办?那么我将如何从那里存根FooFunc(),因为在OuterFunction()中创建了注入的依赖项?
我是否必须通过在main中创建&Foo {}结构从OuterFunction()中创建一个级别,然后将其注入OuterFunction()?
换句话说,您将如何测试创建和注入依赖项的函数?
通常当人们谈论依赖注入时,它是在创建对象(或在Go的case结构中)的上下文中。
在Go中执行此操作的规范方法是使用New
函数,例如object_name.New(...)
或package_name.NewObjectName(...)
。这些函数接受对象依赖项并输出对象的实例。
上面你在静态函数中做了很多代码。是否可以将其转换为已创建的对象并在其上设置方法?这有时被称为控制反转。
type Foo {
bar Bar
baz Baz
}
func NewFoo(bar Bar, baz Baz) *Foo {
return &Foo{ bar: bar, baz: baz }
}
func (foo *Foo) X() {
foo.bar.Y()
foo.baz.Z()
}
该模型可以扩展到多个级别,因此测试更容易。
foo := NewFoo(
NewBar(...),
NewBaz(...),
)
这是一个可能有用的post。
如果您使用的是Dargo注入框架,则可以使用更高的Rank绑定您的接口或结构的版本,然后将其用于您的代码而不是普通代码绑定的内容。
假设您在正常代码中有一些像这样定义的服务:
var globalLocator ioc.ServiceLocator
type AnExpensiveService interface {
DoExpensiveThing(string) (string, error)
}
type NormalExpensiveServiceData struct {
}
func (nesd *NormalExpensiveServiceData) DoExpensiveThing(thingToDo string) (string, error) {
time.Sleep(5 * time.Second)
return "Normal", nil
}
type SomeOtherServiceData struct {
ExpensiveService AnExpensiveService `inject:"AnExpensiveService"`
}
func init() {
myLocator, err := ioc.CreateAndBind("TestingExampleLocator", func(binder ioc.Binder) error {
binder.Bind("UserService", SomeOtherServiceData{})
binder.Bind("AnExpensiveService", NormalExpensiveServiceData{})
return nil
})
if err != nil {
panic(err)
}
globalLocator = myLocator
}
func DoSomeUserCode() (string, error) {
raw, err := globalLocator.GetDService("UserService")
if err != nil {
return "", err
}
userService, ok := raw.(*SomeOtherServiceData)
if !ok {
return "", fmt.Errorf("Unkonwn type")
}
return userService.ExpensiveService.DoExpensiveThing("foo")
}
现在您不想在测试代码中调用昂贵的服务。在以下测试代码中,昂贵的服务被替换为具有更高等级的模拟服务。当测试调用用户代码时,使用mock来代替普通的昂贵代码。这是测试代码:
type MockExpensiveService struct {
}
func (mock *MockExpensiveService) DoExpensiveThing(thingToDo string) (string, error) {
return "Mock", nil
}
func putMocksIn() error {
return ioc.BindIntoLocator(globalLocator, func(binder ioc.Binder) error {
binder.Bind("AnExpensiveService", MockExpensiveService{}).Ranked(1)
return nil
})
}
func TestWithAMock(t *testing.T) {
err := putMocksIn()
if err != nil {
t.Error(err.Error())
return
}
result, err := DoSomeUserCode()
if err != nil {
t.Error(err.Error())
return
}
if result != "Mock" {
t.Errorf("Was expecting mock service but got %s", result)
return
}
}
当调用DoUserCode时,查找UserService而不是获得正常的实现,而是注入模拟。
之后,测试只是验证它是注入的模拟而不是正常的代码。
这是Dargo进行单元测试的基础知识!我希望它有所帮助