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