Чем отличаются юнит-тесты от интеграционных тестов

Ответ

Юнит-тест (Unit Test) проверяет наименьшую изолированную часть кода (модуль, функцию, класс). Интеграционный тест (Integration Test) проверяет взаимодействие нескольких компонентов системы между собой.

Критерий Юнит-тесты Интеграционные тесты
Цель Проверить корректность логики одного модуля Убедиться, что компоненты правильно работают вместе
Изоляция Внешние зависимости (БД, API) заменяются моками Используют реальные или тестовые зависимости (тестовая БД)
Скорость Очень быстрые Значительно медленнее
Область Узкая, сфокусированная Широкая, охватывает несколько слоев

В пирамиде тестирования юнит-тесты составляют основу (их должно быть больше всего), а интеграционные тесты находятся на следующем уровне.

Пример юнит-теста (проверка одной функции):

# a_module.py
def is_strong_password(password: str) -> bool:
    return len(password) >= 8 and any(c.isdigit() for c in password)

# test_a_module.py
def test_is_strong_password():
    assert is_strong_password("password123") == True
    assert is_strong_password("short") == False
    assert is_strong_password("no_digits") == False

Пример интеграционного теста (проверка связки "сервис + БД"):

# user_service.py
class UserService:
    def __init__(self, db_session):
        self.db = db_session

    def create_user(self, name):
        user = User(name=name)
        self.db.add(user)
        self.db.commit()
        return user

# test_user_service.py
def test_user_creation_in_db(test_db_session): # test_db_session - реальное подключение к тестовой БД
    service = UserService(test_db_session)
    user = service.create_user("John Doe")

    # Проверяем, что пользователь действительно появился в базе данных
    retrieved_user = test_db_session.query(User).filter_by(name="John Doe").one()
    assert user.id == retrieved_user.id

Ответ 18+ 🔞

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

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

Интеграционный тест — это уже проверка, как несколько этих деталей, блядь, срастаются в одно целое. Тут уже мокать нельзя, нужно поднимать реальную (или максимально близкую к реальной) инфраструктуру: тестовую базу, заглушки API и прочую хуету. Он медленный, как черепаха в сиропе, но показывает, не развалится ли всё нахуй при сборке.

Критерий Юнит-тесты Интеграционные тесты
Цель Убедиться, что одна функция не гонит пургу Убедиться, что несколько функций не устроят между собой пиздец при работе
Изоляция Все вокруг — муляжи и подделки (моки) Используется почти настоящая обстановка (тестовая БД и т.д.)
Скорость Быстрее, чем ты успеешь сказать «ёпта» Медленнее, чем ожидание ответа от поддержки
Область Одна маленькая пиздюлина Целый сценарий из нескольких пиздюлин

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

Вот тебе пример юнит-теста, где мы проверяем одну функцию, отъединённую от всего мира:

# a_module.py
def is_strong_password(password: str) -> bool:
    return len(password) >= 8 and any(c.isdigit() for c in password)

# test_a_module.py
def test_is_strong_password():
    assert is_strong_password("password123") == True
    assert is_strong_password("short") == False
    assert is_strong_password("no_digits") == False

Видишь? Никаких баз данных, никаких сетей. Чистая логика. Либо работает, либо нет.

А теперь пример интеграционного теста, где мы проверяем, как сервис общается с базой данных:

# user_service.py
class UserService:
    def __init__(self, db_session):
        self.db = db_session

    def create_user(self, name):
        user = User(name=name)
        self.db.add(user)
        self.db.commit()
        return user

# test_user_service.py
def test_user_creation_in_db(test_db_session): # test_db_session - реальное подключение к тестовой БД
    service = UserService(test_db_session)
    user = service.create_user("John Doe")

    # Проверяем, что пользователь действительно появился в базе данных
    retrieved_user = test_db_session.query(User).filter_by(name="John Doe").one()
    assert user.id == retrieved_user.id

Вот тут уже пошла интеграция: сервис + сессия базы данных. Если что-то не так сгенерировался SQL, или транзакция не закоммитилась — тест словит этот пиздец. Но и бегать он будет, как сонная муха.

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