Прямой ответ
Пирамида тестирования — модель распределения тестов по уровням: внизу много быстрых и дешёвых unit-тестов, выше меньше интеграционных, на вершине немного сквозных (end-to-end). Смысл не в точных числах, а в принципе: чем выше уровень, тем дороже тест и тем меньше их должно быть.
Уровни и их свойства:
| Уровень | Что проверяет | Скорость | Локализация ошибки |
|---|---|---|---|
| Unit | Отдельная функция или класс | Миллисекунды | Точная |
| Интеграционный | Модуль с реальной БД, очередью, API | Секунды | Хорошая |
| Контрактный | Совместимость двух сервисов | Секунды | Хорошая |
| E2E | Сценарий пользователя во всей системе | Десятки секунд — минуты | Плохая |
| Ручной / исследовательский | То, что автоматизировать дорого | Очень медленно | — |
Почему пропорция именно такая:
- Скорость обратной связи. Тысяча unit-тестов выполняется за минуту; сотня e2e — за полчаса. Разработчик запускает первое десятки раз в день, второе — перед выкаткой.
- Локализация. Падение unit-теста указывает на конкретную функцию. Падение e2e-сценария «оформление заказа» может быть вызвано десятком причин.
- Стоимость поддержки. E2E-тесты ломаются от изменений интерфейса, таймингов и данных. Их поддержка стоит дороже написания — см. flaky-тесты.
- Надёжность. Чем больше внешних компонентов участвует, тем выше вероятность ложного падения.
Обратная пирамида (много e2e, мало unit) — распространённый антипаттерн. Её признаки: прогон занимает час, падения разбирают «перезапуском», разработчики боятся менять код, потому что не понимают, что сломалось. Часто она возникает исторически: e2e-тесты пишут QA-инженеры, а unit-тесты не пишутся вовсе.
# маркеры pytest позволяют запускать уровни раздельно
# pyproject.toml
[tool.pytest.ini_options]
markers = [
"unit: быстрые тесты без внешних зависимостей",
"integration: требуется БД или контейнер",
"e2e: сквозной сценарий, запуск перед выкаткой",
]
pytest -m unit # на каждое сохранение
pytest -m "unit or integration" # на каждый коммит
pytest -m e2e # перед выкаткой
Практика: как применить в реальном проекте
1. Ориентиры пропорций. Точных чисел не существует, рабочие диапазоны: unit 60–80 % от числа тестов, интеграционные 15–30 %, e2e 5–10 %. Важнее другое: время прогона unit-тестов — минуты, полного — десятки минут.
2. Unit-тест — это не «тест маленького класса». Критерий — отсутствие внешних зависимостей и детерминированность. Расчёт скидки, парсинг, бизнес-правила, преобразования данных. Внешние зависимости подменяют двойниками — см. тестовые двойники.
3. Интеграционные тесты — на границах. Реальная БД проверяет SQL, миграции, ограничения и транзакции. Реальная очередь — семантику доставки. Контейнеры позволяют делать это воспроизводимо — см. интеграционные тесты в Docker.
4. E2E — только ключевые сценарии. Регистрация, оплата, отмена. Десять-двадцать сценариев, а не двести. Их цель — убедиться, что система собирается в работающее целое, а не проверить каждую ветку.
5. Контрактные тесты закрывают промежуток. Они проверяют совместимость сервисов без развёртывания обоих — это дешевле e2e и надёжнее. Подробно — в статье про Pact.
6. Не всё нужно автоматизировать. Проверка визуальной части, удобства, формулировок — задача ручного тестирования. Попытка автоматизировать это даёт дорогие и хрупкие тесты.
Рекомендации:
- Измеряйте время прогона по уровням. Если unit-прогон занимает 20 минут, пирамида уже нарушена: либо тесты не изолированы, либо они делают лишнюю работу.
- Пишите тест на том уровне, где дефект дешевле всего ловится. Ошибка в расчёте — unit; ошибка в запросе — интеграционный; ошибка в сборке системы — e2e.
- Не дублируйте проверки между уровнями. Если правило проверено unit-тестом, e2e-сценарий должен проверять связность, а не то же правило ещё раз.
- Пересматривайте набор e2e. Сценарии, которые не падали ни разу за год и не менялись, вероятно, проверяют не то, что ломается.
- Следите за обратной связью. Тест, результат которого разработчик ждёт дольше пяти минут, фактически не участвует в процессе.
Практический сценарий: разворот обратной пирамиды
Контекст: в проекте было 340 e2e-тестов в Selenium и 60 unit-тестов. Полный прогон занимал 2 часа 10 минут, в среднем 11 % прогонов падали без изменений кода.
- Измерение. Разбор показал: 78 % e2e-тестов проверяли правила расчёта и валидации, доступные на уровне функций. Только 41 тест действительно проверял сквозной сценарий.
- Шаг 1. Правила расчёта и валидации перенесли в unit-тесты: 210 новых тестов, время прогона 40 секунд.
- Шаг 2. Взаимодействие с базой и очередями покрыли интеграционными тестами в контейнерах: 85 тестов, 4 минуты.
- Шаг 3. В e2e оставили 38 сценариев, соответствующих ключевым пользовательским путям; остальные удалили.
- Шаг 4. Разделили прогоны: unit — на каждое сохранение, плюс интеграционные — на каждый коммит, e2e — перед выкаткой и ночью.
- Результат. Обратная связь по unit-тестам — 40 секунд вместо ожидания ночного прогона. Доля ложных падений снизилась с 11 до 2 %. Время полного прогона — 26 минут. Число дефектов, найденных до продакшена, за квартал выросло, потому что разработчики начали запускать тесты регулярно.
Вывод: пирамида — это про скорость обратной связи. Перенос проверок на уровень ниже окупается не экономией машинного времени, а тем, что тесты начинают запускать.
FAQ
Обязательна ли форма пирамиды? Форма — следствие свойств уровней: стоимости, скорости и локализации. Для приложений с тонкой бизнес-логикой и сложным интерфейсом разумная пропорция смещается вверх; для библиотек и алгоритмических систем — вниз.
Что такое «мороженое» (ice cream cone)? Разновидность обратной пирамиды: много ручных проверок и e2e поверх малого числа автоматизированных тестов. Признак — тестирование в основном выполняется людьми перед релизом.
Нужны ли e2e-тесты вообще, если есть интеграционные и контрактные? Да, но немного. Они проверяют то, что не проверяется иначе: сборку системы, конфигурацию окружения, поведение под нагрузкой реального клиента.
Как понять, что тестов на уровне достаточно? Косвенные признаки: дефекты соответствующего типа перестают доходить до следующих уровней, а падение теста даёт понятную локализацию. Если дефект в расчёте обнаруживается в e2e — unit-уровень недопокрыт.
Источники: Martin Fowler (Practical Test Pyramid), pytest docs (markers), Google Testing Blog — ссылки в шапке.