Что это?

Gomock - фиктивный фреймворк для модульного тестирования на Go. Это похоже на Mockito на Java.

Установка

go get github.com/golang/mock/gomock
go get github.com/golang/mock/mockgen

Пример в первую очередь

Представьте, что мы разрабатываем программное обеспечение для автомобиля, в котором основной двигатель (Car) управляет различными компонентами, такими как цепь запуска, калибратор и ускоритель. Когда двигатель запускается, он сначала вращает коленчатый вал и калибрует систему. Во время этого процесса никогда не должно выполняться ускорение.

В этом примере у нас есть следующие интерфейсы: Car, CrankingCircuit, Calibrator и Accelerator. Структура проекта выглядит так:

Для начала нам нужно сгенерировать макеты на основе интерфейсов:

mockgen -source=car_parts.go -destination=car_parts_mock.go -package=automobile

А теперь давайте напишем наш первый тест.

package automobile

import (
   "testing"

   . "github.com/golang/mock/gomock"
   "github.com/stretchr/testify/assert"
)
func TestCar_Ignite___should_ignite_successfully(t *testing.T) {
   controller := NewController(t)
   defer controller.Finish()

   crankingCircuit := NewMockCrankingCircuit(controller)
   crankingCircuit.EXPECT().RotateShaft().Return(nil).Times(2)

   calibrator := NewMockCalibrator(controller)
   calibrator.EXPECT().Calibrate(CalibrationParams{
      FuelLevel: 2,
      Latitude:  123,
      Longitude: 4567,
   }).Return(true, nil)

   accelerator := NewMockAccelerator(controller)
   car := New(
      crankingCircuit,
      calibrator,
      accelerator,
   )
   assert.NoError(t, car.Ignite(IgnitionParams{
      FuelLevel:   2,
      CoordinateX: 123,
      CoordinateY: 4567,
   }))
}

Контроллер mock требуется для каждого теста, потому что он отслеживает все вызовы имитаций и не выполнит тесты, если метод вызывается (или не вызывается) неожиданно.

За EXPECT() следует объявление ожидаемых входов и выходов для каждого из методов имитаций. Gomock выполняет точное сопоставление входных данных и будет жаловаться, если не будет вызова метода с точными параметрами. Для одного и того же метода вы можете определить несколько пар ввода / вывода. Обратите внимание, что количество и тип параметров и значений результатов должны соответствовать тому, что было определено в интерфейсе. Например, Calibrate() получает 1 параметр типа CalibrationParams и возвращает 2 значения boolean и error.

Если вас не особо интересуют значения входных данных, вы можете использовать подстановочный знак Any ():

calibrator.EXPECT().Calibrate(Any()).Return(true, nil)

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

calibrator.EXPECT().Calibrate(Any()).DoAndReturn(
   func(params CalibrationParams) (calibrated bool, err error) {
      // checks whatever
      return true, nil
   })

Гомок также строго придерживается требований к количеству обращений к конкретному методу. Нам нужно определить, сколько раз он вызывается с помощью метода Times(). Если это не важно, можно просто использовать AnyTimes():

crankingCircuit.EXPECT().RotateShaft().Return(nil).AnyTimes()

Gomock также предоставляет другие полезные функции, такие как Matcher, который помогает вам определять настраиваемое сопоставление входных данных в случае, если сопоставление по умолчанию не может обрабатывать сравнение для текущих типов входных данных. Еще одна заслуживающая упоминания особенность - это возможность обеспечивать порядок вызовов внутри метода (InOrder).

Зачем нам его использовать?

Теперь, когда мы рассмотрели этот пример, вы, возможно, осознали некоторые преимущества использования Gomock или любой подобной имитирующей среды.

  • Изолированное тестирование логики компонентов и зависимостей

Как можно быть настолько уверенным, что дефект, обнаруженный во время тестирования, принадлежит тестируемому компоненту или его зависимостям? Моки гарантируют, что тестируемый объект изолирован. Он также поощряет писать простую реализацию имитационного метода, в основном только с входами и выходами. Чем больше логики добавлено в макет реализации, тем больше вероятность, что она будет ошибочной.

  • Обеспечьте зависимость от интерфейсов, а не от конкретных объектов

Чтобы имитировать зависимость, она должна быть определена как интерфейс. В результате два компонента станут слабосвязанными.

Подводные камни

  • Чрезмерное использование Any() и / или AnyTimes()

Гибкость противоречит правильности тестов. Если вы знаете, какие будут входные данные, их лучше проверить.

  • Чрезмерное использование DoAndReturn(interface{})

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

Если вам понравилась статья, обратите внимание: