В чем разница между модульными (Unit), интеграционными и сквозными (End-to-End) тестами?

Ответ

Это основные уровни тестирования, образующие тестовую пирамиду. Они различаются по объему проверяемого кода, скорости выполнения и сложности поддержки.

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

  • Цель: Проверить корректность работы отдельной функции, метода или класса в полной изоляции.
  • Объем: Минимальный. Все внешние зависимости (база данных, API, файловая система) заменяются моками или стабами.
  • Скорость: Очень высокие.
  • Пример (Python/pytest):
    def test_calculate_discount():
    # Изолированный тест чистой функции
    assert calculate_discount(100, 10) == 90
    assert calculate_discount(0, 10) == 0

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

  • Цель: Проверить взаимодействие между несколькими компонентами системы (например, сервис + база данных, два микросервиса).
  • Объем: Средний. Используются реальные или приближенные к реальным зависимости (тестовая БД, заглушки сервисов).
  • Скорость: Средние.
  • Пример (проверка работы с БД):
    def test_user_repository_saves_to_db(db_session):
    repo = UserRepository(db_session)
    user = User(name="Alice")
    saved_user = repo.save(user)
    # Проверяем реальное взаимодействие с БД
    assert saved_user.id is not None
    assert db_session.query(User).filter_by(id=saved_user.id).first() is not None

Сквозные тесты (End-to-End / E2E Tests)

  • Цель: Имитировать полный путь пользователя через приложение, проверяя всю систему целиком (UI, бэкенд, БД, сети).
  • Объем: Максимальный. Запускается всё приложение в среде, максимально близкой к продакшену.
  • Скорость: Низкие.
  • Пример (Cypress для веб-приложения):
    describe('Пользовательский поток покупки', () => {
    it('пользователь может добавить товар в корзину и оформить заказ', () => {
    cy.visit('/products');
    cy.get('[data-testid="product-card"]').first().click();
    cy.get('[data-testid="add-to-cart"]').click();
    cy.visit('/cart');
    cy.contains('Оформить заказ').click();
    cy.url().should('include', '/checkout');
    cy.get('[data-testid="order-success"]').should('be.visible');
    });
    });

Ключевое правило (Пирамида тестов): Много быстрых и дешёвых модульных тестов в основании, меньше интеграционных и совсем мало медленных и дорогих E2E-тестов на вершине.

Ответ 18+ 🔞

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

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

def test_calculate_discount():
    # Тестируем просто математику, без всего лишнего
    assert calculate_discount(100, 10) == 90
    assert calculate_discount(0, 10) == 0

Вот видишь? Никаких баз данных, никаких сетей. Чистая арифметика. Если этот тест упал — ты, чувак, просто ебанько, который не умеет вычитать.

Интеграционные тесты (Integration Tests) А вот тут уже начинается веселье. Это когда ты проверяешь, как твои кирпичики друг с другом склеиваются. Работает ли сервис с реальной (ну или почти реальной) тестовой базой? Общаются ли два твоих микросервиса без драки? Объём уже побольше, скорость помедленнее, потому что надо поднимать какие-то настоящие штуки.

def test_user_repository_saves_to_db(db_session):
    repo = UserRepository(db_session)
    user = User(name="Alice")
    saved_user = repo.save(user)
    # А вот тут уже реально пишем в БД и читаем из неё
    assert saved_user.id is not None
    assert db_session.query(User).filter_by(id=saved_user.id).first() is not None

Смысл в чём? А в том, что функция save может быть идеальной, но если ты, хитрая жопа, криво написал SQL-запрос, то всё — доверия ебать ноль. Интеграционный тест это и ловит.

Сквозные тесты (End-to-End / E2E Tests) Ну а это, ёпта, полный пиздец и апофеоз всего. Ты садишься за руль всего этого хуя в пальто под названием «твоё приложение» и едешь от точки А (пользователь открыл сайт) до точки Б (получил заказ). Запускается ВСЁ: интерфейс, бэкенд, базы, сети — полный комплект. Скорость — хуй с горы, потому что браузер грузится, анимации крутятся, а ты сидишь и терпения ноль ебать.

describe('Пользовательский поток покупки', () => {
  it('пользователь может добавить товар в корзину и оформить заказ', () => {
    cy.visit('/products');
    cy.get('[data-testid="product-card"]').first().click();
    cy.get('[data-testid="add-to-cart"]').click();
    cy.visit('/cart');
    cy.contains('Оформить заказ').click();
    cy.url().should('include', '/checkout');
    cy.get('[data-testid="order-success"]').should('be.visible');
  });
});

Удивление пиздец, если этот тест зелёный. Значит, вся твоя конструкция, против всех ожиданий, ещё жива.

Главное правило, чувак (Пирамида тестов): Внизу, в основании, у тебя должна быть куча быстрых и дешёвых модульных тестов (проверил каждый кирпич). Чуть выше — поменьше, но уже посерьёзнее интеграционных (проверил, что стены не развалятся). И на самом верху, совсем чуть-чуть, медленные и дорогие сквозные тесты (проверил, что в доме можно жить). Если у тебя эта пирамида перевёрнута и ты пишешь одни только E2E — ты, бля, распиздяй, который просто любит страдать.