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

В чём разница между моками и стабами?

1.0 Junior🔥 61 комментариев
#Тестирование

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

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

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

Разница между моками и стабами в тестировании

В контексте модульного тестирования (unit testing) и тестирования в целом, моки (mocks) и стабы (stubs) — это два типа тестовых заглушек (test doubles), которые используются для изоляции тестируемого кода от его зависимостей. Хотя оба понятия часто путают, они имеют принципиально разные цели и поведение.

Ключевое концептуальное отличие

Основное различие лежит в цели их использования:

  • Стабы подменяют реальные зависимости, чтобы предоставить тесту контролируемые данные.
  • Моки подменяют реальные зависимости, чтобы проверить взаимодействие (behavior) между тестируемым кодом и зависимостью.

Подробное сравнение

Стабы (Stubs)

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

Характеристики стабов:

  • Предоставляют жестко заданные возвращаемые значения.
  • Не проверяют, как они были вызваны.
  • Используются для управления состоянием (state-based testing).
  • Обычно не содержат assertions (проверок).

Пример стаба на Go:

// Интерфейс зависимости
type UserRepository interface {
    FindByID(id int) (*User, error)
}

// Стаб для теста
type StubUserRepository struct{}

func (s *StubUserRepository) FindByID(id int) (*User, error) {
    // Всегда возвращаем предопределённого пользователя
    return &User{ID: 1, Name: "Test User"}, nil
}

// В тесте
func TestGetUserService_WithStub(t *testing.T) {
    repo := &StubUserRepository{}
    service := NewUserService(repo)
    
    user, err := service.GetUser(1)
    
    // Проверяем результат работы service, а не вызовы в repo
    if err != nil {
        t.Errorf("unexpected error: %v", err)
    }
    if user.Name != "Test User" {
        t.Errorf("expected Test User, got %s", user.Name)
    }
}

Моки (Mocks)

Моки — это "умные" заглушки, которые запоминают, как их вызывали, и позволяют проверить эти вызовы после выполнения теста. Они содержат expectations (ожидания) того, как должны быть вызваны.

Характеристики моков:

  • Проверяют взаимодействие между объектами.
  • Содержат assertions для проверки вызовов.
  • Используются для поведенческого тестирования (interaction testing).
  • Могут проверять количество вызовов, аргументы, порядок вызовов.

Пример мока на Go с использованием библиотеки testify:

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

// Мок-объект
type MockUserRepository struct {
    mock.Mock
}

func (m *MockUserRepository) FindByID(id int) (*User, error) {
    args := m.Called(id)
    return args.Get(0).(*User), args.Error(1)
}

// В тесте
func TestGetUserService_WithMock(t *testing.T) {
    mockRepo := new(MockUserRepository)
    service := NewUserService(mockRepo)
    
    // Настраиваем ожидания
    expectedUser := &User{ID: 1, Name: "Test User"}
    mockRepo.On("FindByID", 1).Return(expectedUser, nil).Once()
    
    user, err := service.GetUser(1)
    
    // Проверяем, что мок был вызван как ожидалось
    mockRepo.AssertExpectations(t)
    
    // Дополнительные проверки результата
    if err != nil {
        t.Errorf("unexpected error: %v", err)
    }
    if user != expectedUser {
        t.Error("expected specific user instance")
    }
}

Практические рекомендации по выбору

Когда использовать стабы:

  1. Тестирование алгоритмов, которым нужны конкретные входные данные.
  2. Симуляция исключительных ситуаций (например, возврат ошибок от зависимостей).
  3. Изоляция от внешних систем (БД, API, файловая система).
  4. Когда важно что возвращает зависимость, а не как она вызывается.

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

  1. Проверка правильности взаимодействия между компонентами.
  2. Верификация отправки сообщений/событий (например, в шину событий).
  3. Тестирование side effects — когда код должен вызывать определенные методы.
  4. Когда важно сколько раз и с какими параметрами вызывается зависимость.

Типичные ошибки и антипаттерны

  1. Избыточное mocking — создание моков для всех зависимостей, даже когда достаточно стабов.
  2. Хрупкие тесты — моки делают тесты чувствительными к изменениям в реализации (тесты начинают проверять не "что", а "как").
  3. Стабы с логикой — стабы не должны содержать условную логику, иначе они становятся сложными для понимания.
  4. Проверка несущественного — моки могут заставлять проверять детали реализации, которые не важны для корректности кода.

Выводы

В Go-экосистеме различие между моками и стабами особенно важно из-за частого использования интерфейсов для внедрения зависимостей. Правильное применение каждого типа заглушек приводит к:

  • Более стабильным тестам (стабы меньше ломаются при рефакторинге)
  • Более точной проверке контрактов (моки явно проверяют взаимодействие)
  • Лучшей читаемости тестов (ясно видно, что именно проверяется)

Современные практики рекомендуют предпочитать стабы мокам, когда это возможно, так как стабы проверяют конечный результат (state), а не промежуточные шаги (behavior), что делает тесты менее хрупкими. Моки следует использовать осознанно, только для проверки критически важных взаимодействий между компонентами.