Чем модульное тестирование отличается от интеграционного?

Ответ

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

Ключевые различия:

Критерий Модульное тестирование Интеграционное тестирование
Объект Один класс или функция. Группа классов, модулей, внешние системы (БД, API).
Изоляция Полная. Зависимости заменяются тестовыми двойниками (Mock, Stub). Минимальная. Используются реальные или приближённые к реальным зависимости.
Скорость Очень быстрое (миллисекунды). Медленное (секунды, минуты).
Цель Проверить корректность логики единицы кода. Проверить корректность взаимодействия и поток данных между компонентами.
Частота запуска При каждом коммите (CI). Реже (ночью, перед релизом).

Пример модульного теста (с Mock):

# Тестируем сервис, изолировав его от реальной базы данных
from unittest.mock import Mock

def test_user_service_get_user():
    # 1. Создаём мок репозитория
    mock_repo = Mock()
    mock_repo.find_by_id.return_value = User(id=1, name="Alice")

    # 2. Внедряем мок в тестируемый сервис
    service = UserService(mock_repo)

    # 3. Вызываем метод и проверяем логику
    result = service.get_user_profile(1)

    # 4. Проверяем, что сервис корректно обработал данные от репозитория
    assert result["name"] == "Alice"
    # Проверяем взаимодействие: метод репозитория был вызван с правильным аргументом
    mock_repo.find_by_id.assert_called_once_with(1)

Пример интеграционного теста:

# Тестируем взаимодействие сервиса с реальной (например, тестовой) БД
def test_user_creation_flow():
    # 1. Поднимаем тестовую БД (Docker, in-memory)
    db = start_test_database()
    repo = UserRepository(db.connection)
    service = UserService(repo)

    # 2. Выполняем операцию, затрагивающую несколько слоёв
    new_user = service.create_user("test@example.com", "password123")

    # 3. Проверяем результат через прямое обращение к БД
    user_from_db = repo.find_by_id(new_user.id)

    # 4. Проверяем согласованность данных и состояние системы
    assert user_from_db is not None
    assert user_from_db.email == "test@example.com"
    assert user_from_db.is_active is True  # Проверка бизнес-логики, применённой при создании

Практическое правило: Пирамида тестов — много быстрых модульных тестов в основании, меньше интеграционных наверху.

Ответ 18+ 🔞

А, ну вот, опять про эти ваши тесты. Слушай, давай я тебе на пальцах объясню, как это всё работает, а то смотрю, ты уже глаза стеклянные делаешь.

Представь себе, блядь, завод по сборке табуреток. Модульный тест — это когда ты берёшь одну ножку, отдельно от всей табуретки, и проверяешь: ровная ли она, не шатается ли, в паз влезает. Всё, хуй с ней, с остальной табуреткой. Только ножка. Изолированно, блядь.

А интеграционный тест — это когда ты эту ножку, сиденье и три другие ножки собрал, хуяк-хуяк, и пытаешься на эту конструкцию сесть. Проверяешь, не развалится ли она под твоей жопой, и все ли детали друг друга понимают.

Вот и вся разница, ёпта. В одном случае ты проверяешь деталь, в другом — как эти детали друг другу не ебут мозг при совместной работе.

Ключевые различия, если по-простому:

Критерий Модульное тестирование Интеграционное тестирование
Что проверяем Одну функцию, один класс. Как будто он один во всей вселенной. Куча классов вместе, а то и целую систему с базой данных или другим сервисом.
Изоляция Полнейшая, блядь. Всех соседей и друзей подменяем муляжами (Mock, Stub). Похуй на изоляцию. Тут уже почти как в бою: реальная база, реальные запросы.
Скорость Быстрее, чем ты успеваешь моргнуть. Медленнее, чем черепаха в сиропе. Иногда ждать овердохуища.
Цель Убедиться, что эта маленькая функция не ебёт какую-то простую логику. Убедиться, что когда одна часть системы плюёт, другая не бежит с тряпкой вытирать, а правильно понимает, что это плевок, а не дождь.
Когда запускаем Постоянно, после каждой строчки кода, как параноики. Реже, когда уже что-то собрали, чтобы не сойти с ума от ожидания.

Вот смотри, как модульный тест выглядит (всё под контролем, все муляжи):

# Тестируем сервис, изолировав его от реальной базы данных
from unittest.mock import Mock

def test_user_service_get_user():
    # 1. Делаем фейковый репозиторий, который всегда возвращает то, что мы скажем
    mock_repo = Mock()
    mock_repo.find_by_id.return_value = User(id=1, name="Alice")

    # 2. Суём этот фейк в наш сервис вместо настоящего
    service = UserService(mock_repo)

    # 3. Дёргаем метод и смотрим, не обосрался ли он
    result = service.get_user_profile(1)

    # 4. Проверяем, что сервис не накосячил с данными, и что он дёрнул репозиторий как надо
    assert result["name"] == "Alice"
    mock_repo.find_by_id.assert_called_once_with(1)

А вот интеграционный тест (тут уже по-взрослому, почти продакшен):

# Тестируем, как всё это хозяйство дружит с реальной (тестовой) БД
def test_user_creation_flow():
    # 1. Поднимаем тестовую базу (хоть в докере, хоть в памяти)
    db = start_test_database()
    repo = UserRepository(db.connection)
    service = UserService(repo)

    # 2. Выполняем целую цепочку: сервис -> репозиторий -> БД
    new_user = service.create_user("test@example.com", "password123")

    # 3. Лезем прямо в базу, проверяем, записалось ли всё
    user_from_db = repo.find_by_id(new_user.id)

    # 4. Смотрим, не насрали ли мы что-то в логике и все ли данные на месте
    assert user_from_db is not None
    assert user_from_db.email == "test@example.com"
    assert user_from_db.is_active is True  # А это бизнес-правило сработало?

И главное правило, чувак: Строй пирамиду. Внизу, в основании, — куча быстрых модульных тестов, которые ловят косяки в мелочах. А наверху, ближе к вершине, — несколько тяжёлых интеграционных, которые проверяют, что вся эта конструкция в целом не развалится нахуй.