В моем недавнем разговоре об Идиоматических трюках Go я говорил о методике тестирования, которую показал мне Дэвид Эрнандес, где вы можете тестировать код, который имеет зависимости (описываемые интерфейсом), сохраняя фиктивный код внутри теста, которому он принадлежит.
Вы пишете структуру, полную функциональных полей, которые отражают интерфейс, и соответствующие методы, которые просто вызывают эти поля.
Это позволяет вам написать тестовый код следующим образом:
func TestCompleteSignup(t *testing.T) { var sentTo string // use the mocked object mockedEmailSender = &EmailSenderMock{ SendFunc: func(to, subject, body string) error { sentTo = to return nil }, } CompleteSignUp("[email protected]", mockedEmailSender) if len(mockedEmailSender.SendCalls()) != 1 { t.Error("one Sender.Send expected") } if sentTo != "[email protected]" { t.Errorf("unexpected recipient: %s", sentTo) } } func CompleteSignUp(to string, sender EmailSender) { // TODO: this }
Вы имитируете функции в EmailSenderMock
, которые, как вы ожидаете, будут вызваны, и можете написать одноразовое настраиваемое поведение для каждого конкретного теста. Вы можете видеть, что имитируемые функции находятся внутри самой тестовой функции, поэтому очень ясно, какие взаимодействия с EmailSender
мы ожидаем при вызове метода CompleteSignUp
.
Внутри имитируемых функций вы можете делать утверждения о том, какие методы вызываются, а также принимать решения о том, какие значения возвращать. Например, вы можете решить вернуть ошибку и убедиться, что код, который вы тестируете, обрабатывает ее правильно.
Проблема с этой техникой заключается в том, что каждый раз, когда вы хотите имитировать интерфейс, приходится писать много шаблонного кода. Вот где появляется Moq ...
Представляем moq
Moq - это простой инструмент, который пишет эти структуры за вас. Вы можете посетить домашнюю страницу проекта по адресу github.com/matryer/moq.
Как это работает
Добавьте комментарий go:generate
над интерфейсом:
package storage //go:generate moq -out store_test.go . Store type Store interface { Get(id string) (interface{}, error) Put(id string, v interface{}) error }
- Флаг
-out
указывает, куда должен быть сохранен вывод. - Точка (
.
) указывает, что интерфейс находится в текущем пакете. Store
означает, что мы хотим имитироватьStore
интерфейс.
Затем запустите go generate
в терминале, и Moq сгенерирует новый файл с именем store_test.go
, содержащий что-то вроде этого:
package storage type StoreMock struct { // GetFunc mocks the Get function. GetFunc func(id string) (interface{}, error) // PutFunc mocks the Put function. PutFunc func(id string, v interface{}) error } // Get calls GetFunc. func (mock *StoreMock) Get(id string) (interface{}, error) { return mock.GetFunc(id) } // Put calls PutFunc. func (mock *StoreMock) Put(id string, v interface{}) error { return mock.PutFunc(id, v) }
Этот код упрощен - фактически сгенерированный код немного сложнее.
Затем вы можете использовать StoreMock
экземпляры в своем тестовом коде везде, где вы тестируете код, основанный на Store
.
Вы можете установить Moq и использовать его сегодня с:
go get github.com/matryer/moq
Как обычно, отзывы и вопросы приветствуются в Twitter @matryer.