Чем отличается тестирование микросервисной архитектуры от тестирования монолита?

Ответ

Ключевое отличие — в распределенной природе микросервисов, что смещает фокус тестирования с модулей на взаимодействие между сервисами.

Аспект Монолит Микросервисы
Основной фокус Модульное и интеграционное тестирование внутри одной кодовой базы. Интеграционное и контрактное тестирование между независимыми сервисами.
Сложность изоляции Низкая. Зависимости — это другие классы/модули в том же процессе. Высокая. Сервисы работают в отдельных процессах, часто на разных хостах. Требуются моки и стабы.
Тестирование отказоустойчивости Обычно не является приоритетом на уровне приложения. Критически важно. Необходимо проверять поведение при падении зависимых сервисов (circuit breakers, retry-логика).
Конфигурация и развертывание Одно окружение, один конфиг. Множество окружений и конфигураций для каждого сервиса. Тестирование конфигов становится отдельной задачей.
Данные для тестов Общая база данных. У каждого сервиса может быть своя БД. Важно управлять тестовыми данными в распределенной транзакции.

Практические примеры для микросервисов:

  1. Использование стабов для изоляции (Python с unittest.mock):

    # test_order_service.py
    from unittest.mock import Mock, patch
    from payment_client import PaymentClient
    
    def test_create_order_with_successful_payment():
        # Создаем мок клиента платежного сервиса
        mock_payment_client = Mock(spec=PaymentClient)
        mock_payment_client.charge.return_value = {'status': 'success', 'transaction_id': 'tx_123'}
    
        # Внедряем мок в тестируемый сервис заказов
        order_service = OrderService(payment_client=mock_payment_client)
    
        result = order_service.create_order(user_id=1, amount=100)
    
        assert result['status'] == 'order_created'
        # Проверяем, что мок был вызван с правильными аргументами
        mock_payment_client.charge.assert_called_once_with(user_id=1, amount=100)
  2. Контрактное тестирование (Pact):

    • Потребитель (Consumer) определяет ожидаемый запрос и ответ от Поставщика (Provider).
    • Почему: Гарантирует, что изменения в API одного сервиса не сломают другого. Каждый сервис может тестироваться и развертываться независимо, имея уверенность в совместимости.
  3. Тестирование сетевых сбоев:

    • Использование инструментов вроде Chaos Engineering (например, Chaos Monkey) для симуляции задержек, таймаутов и недоступности сервисов в тестовом окружении.

Ответ 18+ 🔞

Да ты посмотри, какая разница-то, блядь! Всё дело в том, что эти ваши микросервисы, сука, раскиданы кто куда, как тараканы по кухне. И тестировать теперь надо не то, как один модуль в другом плющит, а как они, падлы, друг с другом через сети общаются.

Штука Монолит (всё в одной куче) Микросервисы (разбежались, как сумасшедшие)
На чём мозги выносим На тестах внутри одной большой программы. На интеграции и контрактах между независимыми сервисами, которые живут своей жизнью.
Сложность отъебаться от соседей Низкая. Зависимости — это просто другие классы рядом. Высокая, пиздец. Сервисы в отдельных процессах, часто на разных серверах. Приходится городить моки и стабы, чтобы не бегать за каждым.
Тестирование "а что, если всё сдохнет?" Обычно всем похуй на уровне приложения. Критически важно, ёпта. Надо проверять, как твой сервис не ляжет, когда соседний накрылся медным тазом (те самые circuit breakers, retry-логика).
Конфиги и запуск Одно окружение, один конфиг. Разобрался — и спи спокойно. У каждого сервиса свой конфиг и своя песочница. Тестирование этих конфигов — отдельная песня, доверия ебать ноль.
Данные для тестов Одна общая база данных. У каждого сервиса своя БД. Надо как-то управлять тестовыми данными в этой распределённой хуйне, чтобы не было каши.

А вот как это выглядит на практике, если не просто языком чесать:

  1. Стабы для изоляции (Python, unittest.mock):

    # test_order_service.py
    from unittest.mock import Mock, patch
    from payment_client import PaymentClient
    
    def test_create_order_with_successful_payment():
        # Делаем фейкового клиента для платежей
        mock_payment_client = Mock(spec=PaymentClient)
        mock_payment_client.charge.return_value = {'status': 'success', 'transaction_id': 'tx_123'}
    
        # Пихаем эту подделку в сервис заказов
        order_service = OrderService(payment_client=mock_payment_client)
    
        result = order_service.create_order(user_id=1, amount=100)
    
        assert result['status'] == 'order_created'
        # Проверяем, что наш фейк вызвали с нужными аргументами
        mock_payment_client.charge.assert_called_once_with(user_id=1, amount=100)
  2. Контрактное тестирование (Pact):

    • Потребитель говорит: "Эй, поставщик, я от тебя вот такой запрос жду и вот такой ответ!".
    • Зачем это, блядь? Чтобы когда один сервис что-то поменял в своём API, другой не сломался с криком "я ничего не знаю!". Каждый может спокойно обновляться, не боясь, что его сосед охуеет.
  3. Тестирование сетевых сбоев:

    • Используем инструменты для Chaos Engineering (типа Chaos Monkey), чтобы в тестовой среде устраивать ад: симулировать задержки, таймауты и полную недоступность сервисов. Проверяем, не развалится ли наша архитектура нахуй при первом же чихе.