Как проверить функцию, которая создает и внедряет зависимость - PullRequest
0 голосов
/ 09 сентября 2018

У меня вопрос: как вы решаете, куда вводить зависимость, и как вы тестируете функцию, в которой зависимость впервые вводится в функцию?

Например, я рефакторинг некоторого кода 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, создавфиктивная структура, которая реализует FooIface с заглушкой FooFunc (), так что здесь все хорошо. Однако, что если я захочу проверить OuterFunction ()?Тогда как бы мне заглушить FooFunc () оттуда, поскольку внедренная зависимость создается в OuterFunction ()?

Должен ли я переместить создание структуры & Foo {} на один уровень выше OuterFunction (), создав его в main, а затем вставив в OuterFunction ()?

Другими словами, как бы вы протестировали функцию, которая создает и внедряет зависимость ?

Ответы [ 2 ]

0 голосов
/ 11 сентября 2018

Если вы используете 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! Я надеюсь, что это поможет

0 голосов
/ 09 сентября 2018

Часто, когда люди говорят о внедрении зависимостей, это происходит в контексте создания объектов (или в случае структур 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(...),
)

Вот сообщение , которое может быть полезным.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...