open atlas
↑ К треку
CI/CD-пайплайны CICD · 02 · 01

Тест-пирамида и обязательные проверки

Сформируй набор пирамидой — много быстрых unit-тестов, меньше integration, тонкий слой e2e — и делай блокирующими мерж только быстрые, надёжные проверки через branch protection. Переверни любое — и gate станет медленным, flaky и недоверенным.

CICD Middle ◷ 16 min
Уровень
ОсновыJuniorMiddleSenior

У команды было 600 end-to-end тестов и 40 unit-тестов. CI шёл 38 минут, падал примерно на каждом третьем прогоне, и никто не верил красной сборке — стандартный совет был «просто перезапусти». Два браузерных теста падали неделями; вместо того чтобы их чинить, кто-то пометил всю e2e-джобу информационной, чтобы мержи проходили. Gate стал декоративным. Набор был перевёрнутой пирамидой, и цена платилась на каждом pull request.

Пирамида: гранулярность, купленная скоростью и доверием

К концу урока ты поймёшь, почему форма тест-набора определяет надёжность гейта мёржа — и как настроить branch protection (защиту ветки), чтобы только нужные проверки его блокировали.

Набор тестов в CI — это портфель, и тест-пирамида («Practical Test Pyramid» Хэма Воке, продолжение идеи Майка Кона) говорит, как его взвесить: много мелких unit-тестов в основании, некоторое количество грубых integration-тестов в середине и очень мало высокоуровневых end-to-end тестов наверху. Форма кодирует трейдофф, который по мере подъёма идёт в противоположные стороны.

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

В середине integration-тесты проверяют, что две реальные части корректно общаются — твой код против реального Postgres в контейнере, реальный HTTP-вызов к застабленной, но точной по протоколу зависимости, ORM против реальной схемы. Они ловят баги, которые unit пропускает (неверный SQL, рассинхрон сериализации, дрейф миграций), ценой секунд на каждый и более тяжёлого сетапа.

Наверху end-to-end тесты гоняют всю систему так, как это делает пользователь — браузер, сервер приложения, база, всё вместе. Они дают наивысшее доверие, что штука реально работает, и они медленные, дорогие и, как пишет Воке, «печально известны своей flaky-природой и часто падают по неожиданным и непредсказуемым причинам». Именно поэтому слой должен оставаться тонким.

СлойОхватСкоростьОтносительное числоГонять в CI когда
UnitОдна функция/модуль, зависимости поддельныемс — тысячи за секундыМного (широкое основание)Каждый push; fail-fast первыми
IntegrationДве реальные части (код + БД, API + протокол)~секунды на каждыйМеньше (середина)Каждый push; после прохода unit
End-to-endВся система через UI~десятки секунд до минутМало (тонкая верхушка)Только критичные пути; flaky в карантин

Градиент частоты — это вся игра: доверие растёт по мере подъёма, но вместе с ним растут время прогона, flakiness и цена отладки. Большую часть безопасности ты покупаешь дёшево в основании и резервируешь дорогую верхушку для горстки критичных пользовательских сценариев — оформление заказа работает, логин работает — которые оправдывают цену. Когда тебя тянет добавить ещё один e2e-тест для граничного случая, спроси себя: не докажет ли быстрый unit-тест в основании то же самое за долю цены?

Почему это работает

Процент coverage — слабый прокси безопасности. Он измеряет, какие строки выполнились во время тестов, а не то, проверил ли ты правильное поведение. Тест, который зовёт функцию и никогда не проверяет её вывод, может поднять coverage до 90%, ничего не доказав; и наоборот, маленький острый тест на ту единственную ветку, что ломается в проде, ценнее сотни строк, покрытых случайно. Совет Воке: «Проверяй наблюдаемое поведение» — утверждай, что входы x и y дают результат z, а не что вызвался конкретный приватный метод. Гонись за поведением, а не за числом.

Ice-cream cone: почему переворот пирамиды бьёт больно

Переверни пирамиду — получишь рожок мороженого (ice-cream cone): тонкий шарик unit-тестов под жирной выпуклостью e2e-тестов — форма из хука. Это кажется безопасным («мы тестируем всё приложение так, как им пользуются!») и это ловушка, о которой Воке прямо предупреждает: она будет «кошмаром в поддержке и слишком долго гоняется».

Три цены складываются. Медленно: end-to-end тесты доминируют по часам на стене, поэтому фидбэк, который должен прийти за минуту, приходит за сорок — а медленный фидбэк — это ровно то, что CI существует предотвращать. Flaky: чем больше системы охватывает тест, тем больше движущихся частей могут икнуть — медленный рендер, гонка, сетевой сбой — поэтому падения перестают коррелировать с реальными багами. Как только красная сборка с равной вероятностью шум или сигнал, люди начинают перезапускать на красном, и набор теряет свою единственную работу. Тяжело отлаживать: упавший unit-тест указывает на одну функцию; упавший e2e-тест значит «где-то, в чём-то из пяти сервисов, что-то пошло не так» — ты получаешь скриншот и таймаут, а не стек-трейс.

Фикс — не удалять e2e-тесты, а спускать утверждения вниз. Большую часть того, что проверяет широкий браузерный тест — правила валидации, форматирование, обработку ошибок, граничные случаи — можно доказать быстрым unit- или integration-тестом. Оставь e2e за незаменимым, что он один доказывает: куски склеены вместе и критичный сценарий проходит до конца.

Gates: как сделать нужные проверки блокирующими мерж

Зелёный набор бесполезен, если кто угодно может смержить красное. Механизм принуждения в GitHub — branch protection: правило на main, которое блокирует прямые пуши и прогоняет изменения через pull request’ы, удовлетворяющие условиям. Рычаги, важные для CI:

  • Required status checks — именованные проверки (твоя unit-джоба, твоя integration-джоба), которые «должны иметь статус successful, skipped или neutral, прежде чем коллабораторы смогут внести изменения в защищённую ветку». Проверка, которой нет в списке обязательных, всё равно гоняется и репортит, но она информационная: она не может блокировать мерж. Это регулятор, которым ты решаешь, что несущее.
  • Require branches to be up to date before merging — настройка strict. С ней PR должен быть перебазирован на свежую базу и заново пройти CI перед мержем, что закрывает дыру семантического конфликта мержа (два PR, каждый проходит в одиночку, но ломаются вместе). Цена — больше перезапусков; на нагруженном репозитории strict может стать бутылочным горлышком merge-очереди.
  • Required reviews — N одобряющих ревью перед мержем, опционально со сбросом устаревших одобрений при новых коммитах.

Вместе эти три рычага образуют защитную оболочку: required status checks обеспечивают корректность, настройка up-to-date закрывает комбинированные поломки, а required reviews добавляют человеческий сигнал. Без хотя бы первого рычага любой из них пуст — ветка с четырьмя одобрениями, но без обязательных проверок, пропускает код, валящий все тесты.

Проектное решение — какие проверки делать блокирующими. Правило большого пальца: гейти на проверках, которые быстрые и надёжные; держи медленные или flaky проверки информационными. Твои unit- и integration-джобы обязательны. Flaky полный e2e-набор не должен быть обязательным — гейтить на нём значит, что недетерминированный тест может блокировать каждый мерж в организации, и «фикс», к которому тянутся люди — отключить gate, ровно как в хуке.

Выбери лучший вариант

9-минутный e2e-набор падает на ~15% прогонов из-за тайминговой flakiness, блокируя несвязанные PR. Что с ним делать как с required-проверкой?

Викторина

У сервиса 95% line coverage и зелёный прогон CI, но баг сериализации уезжает в прод. Как это возможно?

Викторина

На защищённой ветке какая конфигурация И блокирует мерж сломанного кода, И не даёт flaky-тесту блокировать каждый PR?

Вспомните перед уходом
  1. 01
    Почему ice-cream cone (в основном e2e-тесты) делает CI-набор медленным, flaky и недоверенным — и в чём фикс?
  2. 02
    Что такое branch protection и как решать, какие проверки делать required, а какие информационными?
Итог

Тест-пирамида взвешивает CI-набор по гранулярности: широкое основание из множества быстрых unit-тестов, изолирующих один модуль подделками, более узкая середина из integration-тестов, проверяющих две реальные части (твой код против реальной базы или точной по протоколу зависимости), и тонкая верхушка из медленных, дорогих end-to-end тестов, гоняющих всю систему. Доверие растёт по мере подъёма, но вместе с ним — время прогона, flakiness и цена отладки, поэтому большую часть безопасности ты покупаешь дёшево в основании и резервируешь верхушку для горстки критичных сценариев. Переворот в ice-cream cone делает набор медленным, flaky и недоверенным, и фикс — спускать утверждения вниз, а не удалять e2e. Помни, что процент coverage измеряет выполненные строки, а не проверенное поведение — тестируй то, что система делает, а не какие строки выполнились, потому что «всё зелёное» — не то же, что «реально безопасно». Гейтинг — это то, что делает зелёный набор значимым: branch protection с required status checks блокирует мерж кода, проваливающего быстрые надёжные джобы, настройка strict up-to-date закрывает дыру семантического конфликта, и блокирующими ты делаешь только доверенные проверки. Flaky e2e-набору место в неблокирующей карантинной полосе с маленьким required smoke-подмножеством, а не в gate мержа — и CI ты держишь быстрым параллелизмом, шардированием и fail-fast, чтобы gate никогда не стал бутылочным горлышком. Теперь, когда встретишь команду, жмущую «re-run» на каждую красную сборку, ты знаешь диагноз и фикс: привести набор к форме пирамиды и гейтить только на быстром и детерминированном.

Практика

Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.

вспомнитьприменитьуглубить0 из 5 завершено
Связанные уроки

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

Примени это

Примени этот урок в реальном проекте.

Моделирование угроз и закаливание небольшого приложенияВозьми небольшое приложение, которым ты полностью владеешь, и прогони его через цикл, в котором живёт настоящий инженер по безопасности: сначала разберись, как атакующий реально его сломает, а затем закрой эти пути один за другим. Ты построишь дерево атаки против собственного сервиса, а потом починишь аутентификацию, авторизацию, обращение с секретами, заголовки безопасности и валидацию ввода — и запишешь, какое исправление убивает какую ветку дерева. Это и есть всё ремесло защитной безопасности в миниатюре: не чек-лист, а цепочка от угрозы к мере, которую ты сможешь защитить вслух.URL-сокращатель под нагрузкойСобери URL-сокращатель, который выдерживает настоящий трафик, — а потом эксплуатируй его: задеплой, наблюдай и разберись с инцидентом, когда одна горячая ссылка плавит твой кэш.
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.