awesome-everything EN
↑ Обратно к восхождению

Инженерная практика

Дилемма интеграционного тестирования

Суть Поднимать все сервисы вместе работает на двух и рушится на двадцати: e2e медленный, флакает на любом хопе и растёт как ~N², поэтому команды глушат его и не ловят ничего. Пирамида тестов ломается на границе сервиса, где unit-тесты мокают именно то, что и ломается.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на junior-высоте — поверхность
◷ 15 min

В 02:14 сервис orders начинает отдавать 500-е. Команда провайдера переименовала поле с total_cents на amount_cents и выкатила это — чистое, хорошо протестированное изменение, зелёное на их собственном наборе тестов. Ничто в их CI не знало, что три нижестоящих консьюмера читают total_cents. У команды было «полноценное интеграционное окружение», но оно было красным уже девять дней, и все перестали в него смотреть. Первым реальным сигналом для кого-либо стал пейджер, в проде, в пятницу. Фикс занял десять минут. Узнать, что оно сломалось, стоило аварии — потому что единственный тест, который мог это поймать, был тем, которому уже никто не доверял.

Интуитивный ответ не масштабируется

Спросите «будут ли эти два сервиса по-прежнему общаться?» — и очевидный ответ: запусти оба, отправь реальный запрос, проверь ответ. Этот инстинкт верен — для двух сервисов. Беда в том, что происходит по мере роста системы. При N сервисах, вызывающих друг друга, число пар взаимодействий, которые хотелось бы прогнать, растёт примерно как , а end-to-end-тест любого одного сценария требует, чтобы каждый хоп в этом сценарии был здоров в один и тот же момент. Запрос, расходящийся через gateway → orders → pricing → inventory → payments, проходит свой e2e-тест только когда все пять сервисов, плюс их базы данных и очереди, одновременно подняты, промигрированы и засеяны правильными данными.

Требование «все здоровы одновременно» — это тихий убийца. У каждого сервиса, скажем, 98% шанс быть зелёным в общем окружении в любой момент. Свяжите пять вместе — и сценарий зелёный лишь ~90% времени; свяжите двенадцать — и вы уже ниже 80%. Окружение сломано не из-за вашего изменения — оно сломано, потому что чужая миграция применена наполовину, или провалился seed-скрипт, или зависимость в середине деплоя. Весь этот шум вы наследуете на каждом прогоне.

Медленный, флакающий — а значит, игнорируемый

Эти две силы — комбинаторная подготовка и общая хрупкость — порождают три симптома, узнаваемых любой командой на большом e2e-наборе. Медленный: поднять полдюжины сервисов, базу и брокер сообщений до первого ассерта толкает наборы к 40+ минутам, так что они уходят из внутреннего цикла в ночной джоб, за которым никто не следит. Флакающий: поскольку любой хоп может отвалиться по таймауту, большая доля падений — часто называют около 1 из 5 прогонов — не имеет отношения к проверяемому изменению. Игнорируемый: тест, кричащий «волки» четыре раза на каждый реальный баг, глушат, перезапускают-до-зелёного или карантинят. Заглушенный тест не ловит ничего, что строго хуже отсутствия теста, потому что он всё ещё стоит вам времени прогона и даёт ложное спокойствие.

Именно поэтому зрелые платформы переворачивают классический совет. Netflix и Spotify знаменито перекроили «пирамиду» тестов в соты или ромб на уровне сервисов: тонкая шапка настоящих end-to-end-сценариев (ориентир — примерно 5-10% всех тестов), а основная масса уверенности в межсервисной совместимости спущена во что-то более быстрое и изолированное. Вопрос, на который отвечает весь этот юнит: что это за более быстрая и изолированная штука?

Слой тестовЧто поднимаетСкорость / детерминизмЛовит переименование в 02:14?
Unit-тестНичего — граница замоканаМиллисекунды, детерминированНет — мок возвращает старую форму
End-to-endВсе сервисы + БД + очередь вместеМинуты, флакает на любом хопеДа — если окружение зелёное, а оно нет
Недостающий слойОдна сторона, против записанного соглашенияСекунды, детерминированДа — и прямо за столом автора

Граница — именно там, где в пирамиде дыра

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

Так что вас сжимает с двух сторон. Unit-тесты быстры и надёжны, но слепы к границе по построению. End-to-end-тесты могут видеть границу, но слишком медленны и флакающи, чтобы гейтить каждый деплой. Авария с переименованием поля проваливается прямо в эту щель. Нужен тест, который проверяет именно границу — форму и семантику запросов и ответов, которыми обмениваются два сервиса, — не поднимая оба сервиса вместе. Такова форма задачи, которую решает остальной юнит.

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

«Просто держите интеграционное окружение зелёным» звучит как проблема дисциплины, но это проблема структурная. Аптайм общего окружения — это произведение индивидуальных аптаймов всех сервисов, поэтому он деградирует мультипликативно по мере добавления сервисов, а его здоровье принадлежит всем, что означает — никому. Призыв «постарайтесь сильнее» не меняет математику. Единственный долговечный фикс — перестать требовать, чтобы все сервисы были одновременно здоровы, лишь бы узнать, согласны ли два из них.

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

На платформе 18 сервисов с плотными HTTP-зависимостями. E2e-набор идёт 45 минут и падает на ~20% прогонов по причинам, не связанным с изменением. Команда хочет надёжную обратную связь по межсервисной совместимости. Какое направление наиболее здравое?

Викторина

Почему надёжность end-to-end-набора деградирует по мере добавления сервисов в сценарий?

Викторина

Почему unit-тесты не ловят переименование провайдером поля, которое читает консьюмер?

Расставь шаги по порядку

Упорядочьте, как стратегия «только e2e» деградирует до аварии в 02:14:

  1. 1 Два сервиса интегрируются; ради их проверки поднимают общее e2e-окружение
  2. 2 Присоединяется больше сервисов; сценарию нужны они все здоровы одновременно
  3. 3 Набор замедляется за 40 минут и флакает ~1 из 5 по несвязанным причинам
  4. 4 Команда глушит окружение или перестаёт следить; оно красное днями
  5. 5 Провайдер переименовывает поле; единственный тест, что поймал бы это, заглушен; прод пейджит в 02:14
Вспомните перед уходом
  1. 01
    Коллега говорит: «нашему интеграционному окружению просто нужно больше дисциплины, чтобы оставаться зелёным». Объясните, почему это структурная проблема, а не дисциплинарная.
  2. 02
    Где именно пирамида тестов «ломается» на границе сервиса и почему это пропускает переименование поля в прод?
Итог

Инстинкт тестировать интеграцию, запуская все сервисы вместе, верен для двух сервисов и неверен для двадцати. End-to-end-наборы растут комбинаторно: при N взаимодействующих сервисах число пар для прогона масштабируется как N², а любой сценарий проходит, только когда каждый хоп здоров в один и тот же миг, так что надёжность общего окружения — произведение его частей и деградирует мультипликативно по мере добавления сервисов. Результат — знакомая троица: медленно (подъём за 40+ минут), флакающе (~1 из 5 падений не связаны с изменением) и потому заглушено, а заглушенный тест не ловит ничего, при этом всё равно стоит времени прогона. Классическая пирамида не спасает, потому что на сетевой границе unit-тесты мокают провайдера и проверяют устаревшее убеждение автора о его форме, оставаясь зелёными в день, когда провайдер переименовывает поле. Так вас сжимает между слоем «быстрый-но-слепой» и слоем «видит-но-не-доверяют», и межсервисное переименование проваливается в щель — в пейджер в 2 ночи. Нужен слой, который проверяет саму границу — форму и семантику, которыми обмениваются два сервиса, — не требуя поднимать оба вместе. Этот слой — contract testing, и его выстраивание — то, чем занимается остальной юнит.

Связанные уроки
Продолжить восхождение ↑Consumer-driven контракты: истину объявляет консьюмер
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources4
expand
  1. 01
  2. 02
  3. 03
  4. 04

Trademarks belong to their respective owners. Editorial reference only.