В чем разница между модульным (unit) и интеграционным тестированием?

Ответ

Модульное (unit) и интеграционное тестирование — это два разных уровня в пирамиде тестирования, каждый из которых преследует свои цели.

Модульные тесты (Unit Tests)

  • Цель: Проверить корректность работы наименьшей изолированной части кода — "юнита" (обычно это одна функция или метод).
  • Область: Очень маленькая и сфокусированная. Тестируется только логика внутри юнита.
  • Изоляция: Все внешние зависимости (базы данных, файловая система, сетевые вызовы, другие сервисы) должны быть заменены моками (mocks) или стабами (stubs). Это гарантирует, что тест проверяет только тестируемый код, а не его зависимости.
  • Скорость: Очень быстрые, так как не требуют запуска внешних систем. Их должно быть много.

Пример (проверка функции Add):

func Add(a, b int) int {
    return a + b
}

func TestAdd(t *testing.T) {
    result := Add(2, 3)
    if result != 5 {
        t.Errorf("Ожидали 5, получили %d", result)
    }
}

Интеграционные тесты (Integration Tests)

  • Цель: Проверить, что несколько компонентов системы корректно взаимодействуют друг с другом. Они проверяют "швы" и контракты между модулями.
  • Область: Шире, чем у unit-тестов. Например, взаимодействие вашего сервиса с базой данных, с очередью сообщений или с другим API.
  • Изоляция: Частичная. Внешние системы (БД, API) не мокаются, а используются их реальные или тестовые экземпляры (например, тестовая база данных, запущенная в Docker).
  • Скорость: Значительно медленнее, чем unit-тесты, так как требуют настройки окружения и реальных I/O операций.

Пример (проверка создания пользователя в БД):

// db - это реальное подключение к тестовой базе данных
func TestUserCreation(t *testing.T) {
    // 1. Настройка: подключение к тестовой БД и очистка таблиц
    db, cleanup := setupTestDB(t)
    defer cleanup() // 3. Очистка после теста

    repo := NewUserRepository(db)

    // 2. Действие: вызов метода, который работает с реальной БД
    err := repo.CreateUser("test@example.com", "password123")
    if err != nil {
        t.Fatalf("Не удалось создать пользователя: %v", err)
    }

    // Можно добавить проверку, что пользователь действительно появился в БД
}

Сравнительная таблица

ХарактеристикаМодульные тесты (Unit)Интеграционные тесты (Integration)
ЦельКорректность отдельного модуля в изоляцииКорректность взаимодействия между модулями
ЗависимостиЗаменяются моками/стабамиИспользуются реальные (или тестовые экземпляры)
СкоростьОчень высокаяСредняя / Низкая
КоличествоМного (основа пирамиды тестирования)Меньше, чем модульных
Обнаруживаемые ошибкиОшибки в алгоритмах и логике внутри функцииОшибки в контрактах API, конфигурации, схеме БД, сетевом взаимодействии

Заключение: Оба типа тестов критически важны. Unit-тесты обеспечивают надежность отдельных кирпичиков, а интеграционные тесты — что эти кирпичики правильно складываются в стену.