Что такое моки (Mocks) и для чего они используются в тестировании Go-приложений?

Ответ

Мок (Mock) — это объект-двойник, который имитирует поведение реальной зависимости (например, базы данных, внешнего API, файловой системы) в изолированной тестовой среде. Основная цель — проверить, как тестируемый код взаимодействует с этой зависимостью.

Ключевые задачи моков:

  1. Изоляция: Тестировать компонент, не затрагивая его зависимости. Это делает тесты предсказуемыми и быстрыми.
  2. Симуляция состояний: Воспроизводить сценарии, которые сложно или невозможно получить с реальными зависимостями (например, ошибка сети, перегрузка базы данных, специфический ответ от API).
  3. Верификация поведения: Убедиться, что тестируемый код вызывает нужные методы зависимости с правильными параметрами и в верном порядке.

Моки, Стабы, Фейки: в чем разница?

  • Стаб (Stub): Простая заглушка, которая возвращает заранее подготовленные данные. Не проверяет, как её вызывали.
  • Мок (Mock): Объект с заранее заданными ожиданиями (expectations). Тест провалится, если вызовы не будут соответствовать этим ожиданиям (не тот метод, не те аргументы).
  • Фейк (Fake): Упрощенная, но рабочая реализация зависимости. Например, in-memory база данных вместо реальной PostgreSQL.

Пример с gomock в Go:

Предположим, у нас есть интерфейс для работы с базой данных. mockgen сгенерирует для него мок.

// Интерфейс, который мы будем мокировать
type DB interface {
    Get(key string) (string, error)
}

// Тест, использующий сгенерированный мок
func TestService(t *testing.T) {
    // 1. Инициализация контроллера для управления моками
    ctrl := gomock.NewController(t)
    defer ctrl.Finish()

    // 2. Создание экземпляра мока
    mockDB := NewMockDB(ctrl) // NewMockDB генерируется mockgen

    // 3. Установка ожидания: мы ожидаем вызов Get("test")
    // и указываем, что он должен вернуть "value" и nil
    mockDB.EXPECT().Get("test").Return("value", nil)

    // 4. Внедрение мока в тестируемый сервис
    service := NewService(mockDB)
    result, _ := service.DoSomething("test")

    // 5. Проверка результата
    assert.Equal(t, "value_processed", result)
}

Плюсы:

  • Скорость: Тесты выполняются мгновенно, так как нет реальных сетевых вызовов или обращений к диску.
  • Надежность: Тесты не зависят от внешних систем и их доступности.
  • Полнота покрытия: Позволяют легко тестировать обработку ошибок и другие крайние случаи.

Минусы:

  • Хрупкость: Моки тесно связаны с интерфейсом зависимости. При изменении интерфейса придется обновлять все моки и тесты.
  • Риск рассинхронизации: Мок может имитировать поведение, которое уже не соответствует реальной системе, что приведет к ложно-положительным тестам.