2015-11-26 7 views
0

Предположим, у меня есть тип foo по методу largerInt(), который вызывает largeInt(). Я хочу проверить largerInt(), поэтому мне нужно высмеять largeInt(), из-за возможных побочных эффектов.Как я могу издеваться над методом Go?

Я не могу этого сделать, однако. Используя интерфейсы и состав, я могу высмеять largeInt(), но внутри largerInt() он кажется нефиксируемым, так как при его вызове ссылки на тип оболочки отсутствуют.

Любая идея о том, как это сделать? Ниже приведен фрагмент, который я создал, чтобы проиллюстрировать проблему.

Спасибо!

package main 

import (
    "fmt" 
) 

type foo struct { 
} 

type mockFoo struct { 
    *foo 
} 

type MyInterface interface { 
    largeInt() int 
} 

func standaloneLargerInt(obj MyInterface) int { 
    return obj.largeInt() + 10 
} 

func (this *foo) largeInt() int { 
    return 42 
} 

func (this *mockFoo) largeInt() int { 
    return 43 
} 

func (this *foo) largerInt() int { 
    return this.largeInt() + 10 
} 

func main() { 
    myA := &foo{} 
    myB := &mockFoo{} 
    fmt.Printf("%s\n", standaloneLargerInt(myA)) // 52 
    fmt.Printf("%s\n", standaloneLargerInt(myB)) // 53 

    fmt.Printf("%s\n", myA.largerInt()) // 52 
    fmt.Printf("%s\n", myB.largerInt()) // 52 

} 

ответ

2

Поскольку Go не имеет какой-либо формы наследования, вы, вероятно, не сможете получить именно то, что ищете. Тем не менее, есть несколько альтернативных подходов к таким отношениям, которые, как я считаю, работают достаточно хорошо.

Но сначала давайте углубимся в то, что именно происходит в вашем коде. Вы, наверное, уже знаете, большинство из этого, но это могло бы пересчет сделать поведение немного более очевидным:

Когда вы первоначально объявить mockFoo:

type mockFoo struct { 
    *foo 
} 

Это не создает каких-либо реальных отношений между этими двумя типами. Что он делает, так это продвигать методы от foo до mockFoo. Это означает, что к последнему будет добавлен любой метод на foo, который не находится также на mockFoo. Таким образом, это означает, что myB.largerInt() и myB.foo.largerInt() являются идентичными вызовами; нет реальных отношений с foo-> mockFoo, которые можно использовать, как вы выложили.

Это намеренно - часть идеи композиции, противоположной наследованию, заключается в том, что значительно упростить рассуждение о поведении подкомпонентов, ограничивая их взаимодействие.


Итак: где это вас покидает? Я бы сказал, что обычное издевательство не очень хорошо переносит Go, но аналогичные принципы будут. Вместо того, чтобы пытаться «подкласса» foo, создавая обертку, вы должны изолировать все методы изнашиваемой цели в отдельный интерфейс.

Но: что, если вы хотите протестировать методы на foo, которые не имеют побочных эффектов? Вы уже применили одну альтернативу этому: поместите все функции, которые вы хотите протестировать, в отдельные статические методы. Затем foo может делегировать им все свое статическое поведение, и их будет достаточно легко проверить.

Есть и другие варианты, более похожие на структуру, которую вы выложили. Например, вы можете инвертировать отношения между mockFoo и foo:

type foo struct { 
    fooMethods 
} 

type fooMethods interface { 
    largeInt() int 
} 

func (this *foo) largerInt() int { 
    return this.largeInt() + 10 
} 

type fooMethodsStd struct{} 

func (this *fooMethodsStd) largeInt() int { 
    return 42 
} 

var defaultFooMethods = &fooMethodsStd{} 

type fooMethodsMock struct{} 

func (this *fooMethodsMock) largeInt() int { 
    return 43 
} 

var mockedFooMethods = &fooMethodsMock{} 

func main() { 
    normal := foo{defaultFooMethods} 
    mocked := foo{mockedFooMethods} 

    fmt.Println(normal.largerInt()) // 52 
    fmt.Println(mocked.largerInt()) // 53 
} 

Затем вы «подключить» с состоянием компонента структуры, а не управлять им по наследству. Затем вы установили, если до defaultFooMethods во время выполнения, и используйте версию для тестирования. Это немного раздражает из-за отсутствия значений по умолчанию в структурах, но это работает.


Для тех, кто предпочитает композицию над наследованием, это функция, а не ошибка. Произвольно насмешливые методы с побочными эффектами - это грязный бизнес - в самой программе нет ничего, чтобы предлагать, что такое состояние, и должно быть изолировано, а что нет. Принуждение к уточнению отношений заранее может занять больше работы, но делает взаимодействие и поведение кода более очевидным.