首页 文章

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

提问于
浏览
1

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

例如,我正在重构一些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,所以这里一切都很好 . However, what if I wanted to test OuterFunction()? Then how would I stub FooFunc() from there since the injected dependency is created in OuterFunction()?

我是否必须通过在main中创建&Foo {}结构从OuterFunction()中创建一个级别,然后将其注入OuterFunction()?

换句话说, how would you test the function that creates and injects the dependency

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")
    
    }
    

    现在您不想在测试代码中调用昂贵的服务 . 在以下测试代码中,昂贵的服务被模拟服务 with a higher rank 取代 . 当测试调用用户代码时,使用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进行单元测试的基础知识!我希望它有所帮助

相关问题