如何测试创建和注入依赖项的函数

问题描述 投票:1回答:2

我的问题是你如何决定注入依赖项的位置,以及如何测试首次将依赖项注入函数的函数?

例如,我正在重构一些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()?

换句话说,您将如何测试创建和注入依赖项的函数?

unit-testing go dependency-injection
2个回答
0
投票

通常当人们谈论依赖注入时,它是在创建对象(或在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


0
投票

如果您使用的是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进行单元测试的基础知识!我希望它有所帮助

© www.soinside.com 2019 - 2024. All rights reserved.