中文翻译暂不可用,显示俄语原文。
Протестировать delegation paths
ТЕХНИЧЕСКОЕ ЗАДАНИЕ: Протестировать delegation paths
1. Цель задачи
Разработать и выполнить тесты для всех путей делегирования (delegation paths) в распределённой системе с mock downstream, fault injection и валидацией trace. Научиться проверять корректность маршрутизации запросов, тайм-аутов, fallback-логики и обработки ошибок при сбоях внешних сервисов.
Ключевой результат Полный набор тестов (≥10 тестовых сценариев), покрывающий все определённые delegation paths с верификацией через OpenTelemetry traces.
2. Исходные данные
| Что нужно | Откуда взять |
|---|---|
| Документация делегирования (delegation policy, маршруты, fallback) | Предыдущее ТЗ / репозиторий проекта, docs/delegation.md |
| Сервис-делегатор (главный микросервис, принимающий и распределяющий запросы) | Исходный код на Python/Go/Java (реальный или заглушка) |
| Downstream-сервисы (3-5 шт.) | Mock-реализации (например, на unittest.mock, moto, wiremock или встроенные заглушки) |
| OpenTelemetry collector / трейсинг (Jaeger, Zipkin или in-memory exporter) | Установленный OTEL SDK + collector (локальный Docker) |
| Тестовые запросы (payload) и скрипты генерации | Скрипт tests/generate_requests.py (создаётся самостоятельно) |
| Метрики успеха: покрытие путей (edge coverage), средняя задержка, код возврата | Prometheus / OpenMetrics или логи |
Если нет реального инструмента — симулируем:
- Delegation сервис: написать простой Python-сервис на FastAPI с роутером, который на основе заголовка
X-Delegation-Pathнаправляет запрос к одному из трёх mock downstream (mock_a, mock_b, mock_c). Включить тайм-ауты (2 с), fallback (если A не ответил — идти к B), retry (1 попытка). - Mock downstream: использовать asyncio и unittest.mock.AsyncMock для симуляции ответов, задержек, ошибок (500, 503, тайм-аут). Упаковать в отдельный модуль
mocks/. - Trace validation: настроить OpenTelemetry (TracerProvider, SimpleExportSpanProcessor с консольным экспортером или Jaeger в Docker). Каждый путь delegations должен создавать спаны с атрибутами
delegation.path,downstream.name,error.type. - Покрытие путей: собрать карту путей из документации (например: path1: direct A; path2: A→fallback B; path3: A→retry→fallback C и т.д.) и явно задать в тестах.
3. Технологический стек
| Компонент | Инструменты | Назначение |
|---|---|---|
| Сервис-делегатор | Python (FastAPI) + asyncio | Основной сервис под тестирование |
| Mock downstream | unittest.mock, aiohttp / httpx (mock backend) | Эмуляция внешних сервисов |
| Тестирование | pytest, pytest-asyncio, pytest-html (отчёты) | Запуск тестов, assertions |
| Трейсинг | OpenTelemetry SDK для Python, Jaeger (консоль или Docker) | Валидация trace структуры |
| Контейнеризация (опционально) | Docker, docker-compose | Запуск Jaeger, mock-сервисов |
| CI (опционально) | GitHub Actions + pytest-coverage | Автоматический прогон тестов |
4. Этапы выполнения
Этап 1: Подготовка окружения и mock-сервисов (2 часа)
Действия
-
Создать репозиторий (если нет):
git init delegation-tests; cd delegation-tests -
Установить зависимости: в
requirements.txt:fastapi uvicorn pytest pytest-asyncio httpx opentelemetry-api opentelemetry-sdk opentelemetry-instrumentation-fastapi -
Реализовать mock downstream сервис (файл
mocks/downstream_mock.py):import asyncio from fastapi import FastAPI import random app = FastAPI() @app.get("/downstream/{name}") async def downstream(name: str, delay: float = 0.0, fail_prob: float = 0.0): await asyncio.sleep(delay) if random.random() < fail_prob: raise HTTPException(status_code=503, detail="Service Unavailable") return {"status": "ok", "name": name, "delay": delay} -
Создать конфигурацию делегирования (
delegation_config.py):DELEGATION_PATHS = { "direct_a": {"downstream": "downstream_a", "fallback": None, "retry": 0, "timeout": 2.0}, "a_with_retry": {"downstream": "downstream_a", "fallback": None, "retry": 1, "timeout": 2.0}, "a_to_b_fallback": {"downstream": "downstream_a", "fallback": "downstream_b", "retry": 0, "timeout": 2.0}, "a_retry_to_c": {"downstream": "downstream_a", "fallback": "downstream_c", "retry": 1, "timeout": 1.0}, "b_direct": {"downstream": "downstream_b", "fallback": None, "retry": 0, "timeout": 2.0}, } -
Реализовать главный сервис-делегатор (
delegator.py):- Эндпоинт
POST /delegateс телом{"path": "direct_a", "payload": {...}} - Логика: по
pathизDELEGATION_PATHSвыбрать downstream, выполнить запрос черезhttpx.AsyncClientс таймаутом; при ошибке (500, 503, timeout) — применить retry, затем fallback. - Интеграция OTel: создать tracer, для каждого вызова создать span
delegate_stepс атрибутами.
- Эндпоинт
-
Запустить mock сервисы (по одному процессу на каждый downstream, или через docker-compose с разными портами: 8001, 8002, 8003). В целях упрощения — запустить на разных портах в одном процессе, используя threading или asyncio.
Ожидаемый результат этапа Работающий delegator на порту 8000, три мока на портах 8001–8003, конфигурация путей.
Этап 2: Настройка тестирования и сценариев fault injection (2–3 часа)
Действия
-
Создать структуру тестов
tests/test_delegation.py. -
Реализовать фикстуры pytest:
delegator_client— httpx.AsyncClient, указывающий наhttp://localhost:8000mock_downstreams— запуск трёх отдельных uvicorn процессов для моков в pytest сессии (или использоватьmock.patchдля перехвата HTTP-запросов внутри декоратора — проще: заменить реальные запросы на моки черезhttpx_mock).- В примере используем
pytest-httpxдля мокирования исходящих запросов из delegator.
-
Подготовить функции fault injection:
mock_response(status=200, json={"status": "ok"})mock_timeout(delay=3.0)(превышает таймаут delegator)mock_error(status=503)mock_partial_failure(first_fail=True, second_ok=True)для retry
-
Написать матрицу тестовых сценариев (таблица в коде
SCENARIOS):Сценарий path Downstream A Downstream B Downstream C Ожидаемый код ответа Ожидаемая последовательность вызовов 1. direct_a успех direct_a 200, 0s — — 200 [A] 2. direct_a 500 direct_a 500 — — 502 (или fallback none -> ошибка) [A] 3. a_with_retry первый неуспех a_with_retry 503, 2s; затем 200 — — 200 [A, A] 4. a_to_b_fallback A падает a_to_b_fallback timeout (3s) 200, 0s — 200 [A, B] 5. a_retry_to_c A таймаут + retry + fallback a_retry_to_c timeout (1.5s) — 200, 0s 200 [A, A, C] 6. a_retry_to_c все падают a_retry_to_c timeout (1.5s) — 503 502 [A, A, C] 7. b_direct успех b_direct — 200 — 200 [B] 8. неизвестный путь unknown — — — 404 none 9. все моки упали + retry + fallback пуст direct_a (без fallback) все 503 — — 502 [A] (retry? path без retry) 10. timeout на fallback a_to_b_fallback timeout (3s) timeout (3s) — 504 [A, B] -
Реализовать параметризованные тесты с использованием
@pytest.mark.parametrize. -
Добавить проверку trace: после выполнения запроса извлекать из OTel TracerProvider все завершённые спаны через
SpanExporter.get_finished_spans()и проверять атрибуты.
Ожидаемый результат этапа Написанные тесты для 10 сценариев + функция проверки trace.
Этап 3: Запуск тестов, отладка и доработка покрытия (2 часа)
Действия
- Запустить delegator и mock-сервисы (если не сделано ранее) в отдельных терминалах или docker-compose.
- Выполнить тесты:
pytest -v tests/ - Исправить найденные баги:
- Неправильная обработка таймаутов
- Отсутствие fallback при пустом
fallback - Некорректное поведение retry после fallback
- Добавить пропущенные сценарии:
- Тест с большим количеством retry (например, 3)
- Тест с параллельными запросами на одинаковые пути
- Тест на то, что trace не теряется при ошибке
- Проверить покрытие edge coverage:
- Написать скрипт
coverage_report.py, который сравнивает выполненные тесты с полным списком путей из конфига + комбинации ошибок (все возможные комбинации фолбэков). Довести покрытие до 100% по сценариям.
- Написать скрипт
- Задокументировать coverage в
COVERAGE.md.
Ожидаемый результат этапа Все тесты проходят (зелёные), покрытие путей 100%.
Этап 4: Интеграция trace validation и отчётность (1,5 часа)
Действия
- Углубить проверку trace:
- Для каждого теста проверить количество спанов, атрибуты
delegation.path,downstream.name,error.type(если была ошибка). Использоватьopentelemetry.sdk.trace.export.in_memory.InMemorySpanExporter.
@pytest.fixture def span_exporter(): exporter = InMemorySpanExporter() tracer_provider = TracerProvider() tracer_provider.add_span_processor(BatchSpanProcessor(exporter)) # set as global tracer provider trace.set_tracer_provider(tracer_provider) return exporter - Для каждого теста проверить количество спанов, атрибуты
- Реализовать ассерт для trace:
def assert_trace(span_exporter, expected_spans): spans = span_exporter.get_finished_spans() assert len(spans) == len(expected_spans) for span, expected in zip(spans, expected_spans): assert span.attributes.get("downstream.name") == expected["downstream"] - Сформировать HTML-отчёт (pytest-html):
pytest --html=report.html tests/ - Добавить метрики (опционально): эмитировать Prometheus-метрики
delegation_requests_total,delegation_duration_secondsи проверить их через тестовый экспорт.
Ожидаемый результат этапа Отчёт с покрытием, валидация trace корректна.
Этап 5: Документация и ревью (1 час)
Действия
- Написать README.md: как запустить тесты, как добавить новый path.
- Включить таблицу с результатами тестирования delegation paths (пройден/не пройден, комментарии).
- Добавить диаграмму путей (Mermaid) в
docs/delegation_paths.md. - Провести peer review: попросить коллегу проверить сценарии и код.
Ожидаемый результат этапа Готовый пакет тестов с документацией, принят командой.
5. Критерии приемки (Definition of Done)
- Все определённые delegation paths покрыты тестами (≥10 сценариев).
- Каждый тест проверяет код ответа, тело ответа и последовательность вызовов downstream.
- Реализована fault injection (таймауты, ошибки 5xx, частичные успехи).
- Trace validation: каждый тест проверяет количество спанов и атрибуты.
- Тесты могут быть запущены одной командой (
pytest -v). - Покрытие edge coverage для путей (все комбинации fallback + retry) не менее 90% (цель 100%).
- Создан отчёт pytest-html с деталями.
- Написана документация: README, карта путей, конфиг делегирования.
- В CI (GitHub Actions) добавлен шаг запуска тестов.
- Воспроизводимость: тесты работают без внешних зависимостей (все моки локальны).
6. Ожидаемый результат
- Файл/артефакт: репозиторий
delegation-testsс исходным кодом delegator (если создавался) и тестами. - Содержание:
delegator.py— сервис FastAPI (если не был готов).delegation_config.py— конфигурация путей.tests/test_delegation.py— параметризованные тесты.tests/conftest.py— фикстуры (mock, trace exporter).mocks/— mock downstream (если не симулируются через pytest-httpx).requirements.txt/Pipfile.docs/delegation_paths.md— карта путей (Mermaid).README.md— инструкции.report.html— отчёт pytest.
- Опционально:
7. Возможные сложности и их решение
| Сложность | Решение |
|---|---|
| Нет готового delegator | Реализовать минимальный FastAPI сервис как указано в этапе 1. |
| Синхронизация трейсинга с тестами | Использовать InMemorySpanExporter и сбрасывать перед каждым тестом. |
| Тайм-ауты в тестах (ложные срабатывания) | Увеличить лимит времени на тест (pytest-timeout), установить разумные задержки моков (< чем таймаут декоратора). |
| Retry логика и бесконечные циклы | Чётко ограничить количество retry в конфиге, проверять счётчики в тестовых assertion. |
| Неоднородность окружения (порты заняты) | Использовать динамические порты (port=0) и фикстуры с pytest-asyncio + uvicorn. |
| Сложность поддержки большого числа сценариев | Использовать параметризацию со словарём сценариев; добавить автоматическую генерацию из карты путей. |
8. Бюджет времени (оценка)
| Этап | Время (часы) |
|---|---|
| Подготовка окружения и mock-сервисов | 2 |
| Настройка тестов и сценариев fault injection | 3 |
| Запуск тестов, отладка и доработка покрытия | 2 |
| Интеграция trace validation и отчётность | 1,5 |
| Документация и ревью | 1 |
| Итого | 9,5 часов |
Примечание: для первого раза может потребоваться до 16 часов, если делегатор пишется с нуля и не было опыта работы с OTel.
9. Связанные вопросы из базы знаний
| Вопрос | Тема |
|---|---|
| 12 | Что такое delegation path в микросервисной архитектуре |
| 45 | Как эмулировать отказы downstream (fault injection) |
| 78 | Интеграция OpenTelemetry span validation в pytest |
| 101 | Покрытие всех edge-путей в распределённой системе |
| 156 | Обработка тайм-аутов и retry в asyncio |
| 203 | Создание mock-сервисов на FastAPI для тестирования |
| 289 | Использование pytest-httpx для перехвата HTTP-запросов |
| 367 | Best practices для тестирования fallback-логики |
| 412 | Написание документации delegation-policy |
| 503 | Организация CI для тестов с интеграцией трейсинга |
10. Чек-лист самопроверки
- Я корректно настроил OTel tracer и экспортер в тестовых фикстурах.
- Я проверил, что каждый тест явно указывает ожидаемые вызовы downstream.
- Я добавил проверку атрибутов span (delegation.path, downstream.name) для всех сценариев.
- Я убедился, что тесты можно запустить без внешних сервисов (все моки внутри).
- Я зафиксировал покрытие путей и написал
COVERAGE.md. - Я проверил, что отчёт pytest-html генерируется и содержит результаты.
- Я добавил шаг в CI для автоматического прогона тестов.