我的问题是如何确定在哪里注入依赖项,以及如何测试将依赖项首先注入到函数中的函数?
例如,我正在重构一些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
}
我可以通过创建一个模拟的结构轻松地测试InnerFunction,该结构使用存根的FooFunc()实现FooIface,因此在这里一切都很好。 但是,如果我想测试OuterFunction()怎么办?那么既然注入的依赖项是在OuterFunction()中创建的,那我该如何从那里存根FooFunc()?
我是否必须通过在main中创建&Foo {}结构的创建,使其从OuterFunction()中移出一层,然后再将其注入到OuterFunction()中?
换句话说,您将如何测试创建和注入依赖项的功能?
答案 0 :(得分:0)
通常当人们谈论依赖注入时,它是在创建对象的上下文中(或在Go的案例结构中)。
在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,可能会有所帮助。
答案 1 :(得分:0)
如果您使用的是Dargo注入框架,则可以绑定具有更高等级的接口或结构版本,然后将这些等级用于您的代码中,而不是普通代码所绑定的内容。
假设您在常规代码中定义了一些服务,如下所示:
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")
}
现在,您不想在测试代码中调用昂贵的服务。在下面的测试代码中,昂贵的服务由具有较高排名的模拟服务代替。当测试调用用户代码时,将使用模拟代替常规的昂贵代码。这是测试代码:
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进行单元测试的基础!希望对您有帮助