Sobes.tech
Назад к вопросам
Junior — Middle
73

Какие отличия в подходах к тестированию монолитных приложений и микросервисной архитектуры?

Ответ от нейросети

sobes.tech AI

Что хотят услышать интервьюеры:

У монолита тестирование обычно проще по организации: приложение развёрнуто как единое целое, и многие сценарии можно проверять в одном контуре. В микросервисах акцент смещается на контрактное тестирование, проверку интеграций и устойчивости взаимодействий между сервисами. Чем больше сервисов и зависимостей, тем важнее тестировать не только логику, но и границы между компонентами.

Определение:

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

Пример использования:

В монолите интернет-магазина можно поднять приложение целиком, подключить тестовую БД и проверить сценарий оформления заказа через UI или API как единый поток.

В микросервисах того же магазина отдельно тестируют сервис каталога, сервис корзины и сервис оплаты, а затем проверяют, что они корректно взаимодействуют по API и что изменения в одном сервисе не ломают других.

# Псевдокод: разные уровни тестирования для монолита и микросервисов

def test_monolith_checkout(api_client):
    response = api_client.post("/checkout", json={"items": [1, 2]})
    assert response.status_code == 200
    assert response.json()["order_status"] == "created"


def test_service_contract():
    # Проверка ожиданий по API между сервисами:
    # например, сервис оплаты должен принимать определённый формат заказа
    expected_fields = ["order_id", "amount", "currency"]
    actual_fields = get_payment_service_contract_fields()
    assert all(field in actual_fields for field in expected_fields)


def test_microservice_integration(order_client, payment_mock):
    order = order_client.create_order(items=[1, 2])
    assert order["status"] == "pending"

    payment_mock.expect_charge(order_id=order["id"], amount=100)
    result = order_client.pay(order["id"])

    assert result["status"] == "paid"

Пояснение кода:

В примере для монолита показан один интеграционный сценарий: запрос в API проходит через весь приложение-поток до результата оформления заказа.

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

Ещё один пример — интеграционный тест на уровне нескольких сервисов: сервис заказов создаёт заказ, затем через мок или тестовый двойник проверяется взаимодействие с сервисом оплаты. Такой подход помогает тестировать связку, не поднимая все внешние зависимости в полном объёме.

Ключевые моменты:

  • В монолите тесты проще собирать и запускать, потому что приложение обычно тестируется как единое целое.
  • В микросервисах важно добавлять контрактные тесты, чтобы ловить несовместимость API между сервисами.
  • Для микросервисной архитектуры критичны интеграционные тесты, потому что основные проблемы часто возникают на сетевых взаимодействиях.
  • В микросервисах полезны моки, стабы и тестовые окружения, чтобы не зависеть от всех внешних сервисов одновременно.
  • Чем распределённее система, тем больше внимания нужно уделять не только функциональным, но и нефункциональным аспектам: задержкам, отказам, ретраям и идемпотентности.
  • Для обоих подходов важна пирамида тестирования, но в микросервисах обычно выше доля контрактных и интеграционных проверок.