← Назад к вопросам

Что такое моки?

1.2 Junior🔥 211 комментариев
#Тестирование

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что такое моки (Mock-объекты)?

В разработке программного обеспечения, особенно при написании автоматизированных тестов, моки (mock-объекты) — это специальные объекты-заглушки, которые имитируют поведение реальных зависимостей тестируемого кода, но при этом предоставляют инструменты для верификации взаимодействий с ними. В отличие от стабов (stubs), которые просто возвращают предопределённые данные, моки активно следят за тем, как тестируемый код их использует — какие методы вызываются, с какими аргументами и сколько раз.

Ключевые особенности мок-объектов

  1. Верификация взаимодействий (Behavior Verification) Моки позволяют убедиться, что тестируемая система корректно взаимодействует с внешними зависимостями (например, вызывает метод Save() репозитория с определёнными аргументами ровно один раз).

  2. Предопределение поведения (Behavior Specification) Перед тестом настраивается, как мок должен реагировать на вызовы — что возвращать или какие ошибки генерировать.

  3. Изоляция тестируемого кода Моки заменяют реальные зависимости (базы данных, внешние API, файловые системы), делая тесты быстрыми, стабильными и независимыми от внешних систем.

  4. Гибкость и контроль Позволяют тестировать сложные сценарии, включая исключительные ситуации (например, таймауты или сетевые ошибки), которые сложно воспроизвести с реальными компонентами.

Пример использования моков в Go (с библиотекой testify/mock)

Рассмотрим простой сервис, который зависит от репозитория для сохранения данных. Мы протестируем его, заменив реальный репозиторий мок-объектом.

// Предположим, у нас есть интерфейс репозитория
type Repository interface {
    Save(item string) error
    Get(id string) (string, error)
}

// Сервис, который зависит от этого репозитория
type ItemService struct {
    repo Repository
}

func (s *ItemService) ProcessItem(item string) error {
    // Какая-то бизнес-логика...
    if item == "" {
        return errors.New("item cannot be empty")
    }
    // Взаимодействие с зависимостью
    return s.repo.Save(item)
}

Теперь напишем тест с использованием мока:

import (
    "testing"
    "github.com/stretchr/testify/mock"
    "github.com/stretchr/testify/assert"
)

// Создаём мок-структуру, реализующую интерфейс Repository
type MockRepository struct {
    mock.Mock
}

func (m *MockRepository) Save(item string) error {
    args := m.Called(item) // Фиксируем вызов метода
    return args.Error(0)   // Возвращаем заранее заданное значение
}

func (m *MockRepository) Get(id string) (string, error) {
    args := m.Called(id)
    return args.String(0), args.Error(1)
}

func TestItemService_ProcessItem(t *testing.T) {
    // 1. Создаём экземпляр мока
    mockRepo := new(MockRepository)
    service := &ItemService{repo: mockRepo}

    // 2. Настраиваем ожидания: метод Save будет вызван с аргументом "test-item" и вернёт nil
    mockRepo.On("Save", "test-item").Return(nil)

    // 3. Выполняем тестируемый метод
    err := service.ProcessItem("test-item")

    // 4. Проверяем результаты
    assert.NoError(t, err)
    // 5. **ВАЖНО: верифицируем, что ожидаемые вызовы действительно произошли**
    mockRepo.AssertExpectations(t)
}

func TestItemService_ProcessItem_EmptyItem(t *testing.T) {
    mockRepo := new(MockRepository)
    service := &ItemService{repo: mockRepo}

    // НАСТРОЙКА ОЖИДАНИЙ ОТСУТСТВУЕТ — метод Save не должен вызываться!

    err := service.ProcessItem("")

    assert.Error(t, err)
    assert.Equal(t, "item cannot be empty", err.Error())
    // Убеждаемся, что Save() не вызывался
    mockRepo.AssertNotCalled(t, "Save", mock.Anything)
}

Когда использовать моки?

  • Тестирование бизнес-логики, которая зависит от внешних систем (БД, API, очереди сообщений).
  • Проверка корректности взаимодействий между компонентами (например, что после обработки заказа вызывается метод отправки уведомления).
  • Изоляция модулей в юнит-тестах для соблюдения принципа единой ответственности теста.
  • Тестирование сложных сценариев (ошибки сети, таймауты, специфические состояния).

Популярные библиотеки для мокинга в Go

  • testify/mock — наиболее распространённое решение, интегрируется с популярным фреймворком testify.
  • gomock (от Google) — генерация мок-кода на основе интерфейсов через go generate.
  • mockery — генератор моков, часто используется вместе с testify/mock.
  • Ручные моки — самостоятельная реализация интерфейсов для тестирования, что иногда предпочтительнее для простых случаев.

Важные ограничения и рекомендации

  1. Не злоупотребляйте моками — чрезмерное мокирование может привести к хрупким тестам, которые тестируют реализацию, а не поведение.
  2. Моки vs Стабы — используйте стабы, когда нужно просто подменить данные, и моки — когда важно проверить взаимодействие.
  3. Тестируйте поведение, а не реализацию — проверяйте, что система работает правильно, а не просто что вызывается конкретный метод.
  4. В Go отдавайте предпочтение интерфейсам — моки возможны только для абстракций, описанных через интерфейсы.

Моки — мощный инструмент в арсенале разработчика, который при грамотном использовании значительно повышает надёжность и поддерживаемость кода, позволяя писать быстрые, изолированные и предсказуемые юнит-тесты.