В моем недавнем разговоре об Идиоматических трюках 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.