Тестирование распределённых систем
Что ломается на стыках между сервисами — контрактное тестирование, стратегия тестирования микросервисов, асинхронные интеграции и вебхуки, согласованность в конечном счёте, дубликаты и порядок сообщений в очереди, устойчивость при отказе зависимости, проверка выкатки и синхронизация состояния в реальном времени.
10 вопросов
MiddleДизайнЧастоВы — QA фичи загрузки документов. API возвращает 202 Accepted в момент прихода файла; фоновый конвейер затем запускает асинхронное антивирусное сканирование, и лишь после его прохождения отдельный сервис шлёт webhook-callback в ваше приложение и письмо пользователю с уведомлением «готово». Cron-задача ведёт часть шагов и повторяет их при сбое. Когда HTTP-ответ вернулся, ничего ещё не завершено. Спроектируйте подход к тестированию этой интеграции: как проверить конечный результат без фиксированного sleep, как покрыть ветку заражённого файла и ветку «шаг упал и повторился», и как не дать набору проходить лишь когда конвейер отработал быстро?
Вы — QA фичи загрузки документов. API возвращает 202 Accepted в момент прихода файла; фоновый конвейер затем запускает асинхронное антивирусное сканирование, и лишь после его прохождения отдельный сервис шлёт webhook-callback в ваше приложение и письмо пользователю с уведомлением «готово». Cron-задача ведёт часть шагов и повторяет их при сбое. Когда HTTP-ответ вернулся, ничего ещё не завершено. Спроектируйте подход к тестированию этой интеграции: как проверить конечный результат без фиксированного sleep, как покрыть ветку заражённого файла и ветку «шаг упал и повторился», и как не дать набору проходить лишь когда конвейер отработал быстро?
Проверяйте конечное состояние, а не тайминг: опрашивайте callback-endpoint или поле статуса с ограниченным таймаутом и back-off вместо фиксированного sleep. Прогоняйте каждую ветку осознанно — чистый файл, заражённый файл, который скан обязан отклонить, и принудительный сбой шага, чтобы сработал cron-повтор — и проверяйте payload webhook, письмо и идемпотентность при повторной доставке. Ловите callback stub-приёмником, чтобы результат был наблюдаем.
Типичные ошибки
- ✗Заменять опрос фиксированным sleep под текущую скорость конвейера
- ✗Тестировать лишь happy-path, пропуская ветки отклонения и повтора
- ✗Игнорировать идемпотентность, когда повтор доставляет тот же callback
Уточняющие вопросы
- →Чем ограниченный опрос с back-off лучше фиксированного sleep здесь?
- →Как заставить cron-шаг упасть, чтобы в тесте сработал его путь повтора?
MiddleДизайнЧастоОбзор выкатки. Команда вот-вот выпустит рискованное изменение и выбирает из трёх вариантов: canary-деплой, направляющий небольшую долю живого трафика на новую версию, blue-green переключение, поднимающее новую версию рядом со старой и разом переводящее весь трафик, и упрятывание изменения за feature-flag с A-B экспериментом. Верификация на вас. Спроектируйте, как QA проверяет выкатку в каждом из этих случаев — а не только пред-релизную сборку: какие сигналы вы наблюдаете у новой версии против старой, как задать автоматический триггер отката и как удержать честность сравнения A-B или canary, когда две когорты крутят разный код.
Обзор выкатки. Команда вот-вот выпустит рискованное изменение и выбирает из трёх вариантов: canary-деплой, направляющий небольшую долю живого трафика на новую версию, blue-green переключение, поднимающее новую версию рядом со старой и разом переводящее весь трафик, и упрятывание изменения за feature-flag с A-B экспериментом. Верификация на вас. Спроектируйте, как QA проверяет выкатку в каждом из этих случаев — а не только пред-релизную сборку: какие сигналы вы наблюдаете у новой версии против старой, как задать автоматический триггер отката и как удержать честность сравнения A-B или canary, когда две когорты крутят разный код.
Верификация смещается с разовых ворот на живое сравнение: следите за частотой ошибок, задержкой и ключевой бизнес-метрикой новой версии против старого baseline, обслуживающего реальный трафик, а не только против фиксированного порога. Задайте триггер отката заранее — ограниченная регрессия по этим сигналам сама откатывает или сливает canary. Держите сравнение честным, разбивая когорты случайно и сопоставляя сопоставимый трафик, чтобы сдвиг метрики отражал код, а не перекошенную аудиторию или время суток.
Типичные ошибки
- ✗Считать проверку выкатки лишь повторным прогоном пред-релизного набора
- ✗Не задавать автоматический триггер отката по живым сигналам
- ✗Сравнивать когорты с перекошенным трафиком и звать сдвиг результатом кода
Уточняющие вопросы
- →Что делает триггер отката хорошим против шумного?
- →Почему мгновенный флип blue-green рискованнее проверять, чем долю canary?
MiddleДизайнЧастоРешение go/no-go перед релизом платформы ставок в реальном времени. Ставки идут через очередь сообщений между сервисом приёма и движком сведения. При доставке at-least-once очередь может повторно доставить сообщение после падения consumer'а, а при нескольких partitions две ставки могут обработаться не в том порядке, в каком поданы. Владелец продукта хочет выпускать, аргументируя «очередь гарантирует доставку». Вам решать, что проверить до релиза. Спроектируйте подход к тестированию: как доказать, что движок сведения терпит повторную доставку, какая гарантия порядка реально держится и как её проверить, и какое свидетельство нужно, прежде чем подписать корректность ставок.
Решение go/no-go перед релизом платформы ставок в реальном времени. Ставки идут через очередь сообщений между сервисом приёма и движком сведения. При доставке at-least-once очередь может повторно доставить сообщение после падения consumer'а, а при нескольких partitions две ставки могут обработаться не в том порядке, в каком поданы. Владелец продукта хочет выпускать, аргументируя «очередь гарантирует доставку». Вам решать, что проверить до релиза. Спроектируйте подход к тестированию: как доказать, что движок сведения терпит повторную доставку, какая гарантия порядка реально держится и как её проверить, и какое свидетельство нужно, прежде чем подписать корректность ставок.
Проверьте, что обработка идемпотентна: доставьте ту же ставку повторно и убедитесь, что итог не изменился — ни двойной ставки, ни двойного списания. Установите реальную гарантию порядка: очередь упорядочивает лишь в пределах partition или ключа, а не глобально, поэтому направьте ставки по одному лоту на один ключ и проверьте порядок в пределах лота, приняв, что несвязанные ставки чередуются. Подписывайте только когда дубли доказуемо поглощаются, а нужный продукту порядок совпадает с тем, что обеспечивает партиционирование.
Типичные ошибки
- ✗Верить, что at-least-once означает отсутствие дублей у consumer'а
- ✗Ждать глобального порядка, когда очередь упорядочивает лишь в partition
- ✗Считать, что больше partitions ужесточают порядок, а не ослабляют
Уточняющие вопросы
- →Как маршрутизировать ставки, чтобы гарантировать порядок в пределах лота?
- →Как форсировать повторную доставку и доказать, что движок поглощает дубль?
MiddleДизайнЧастоСпор о стратегии в команде: один инженер убеждён, что единственное надёжное покрытие для вашей системы оформления заказа — охватывающей сервис корзины, сервис цен, платёжный сервис и сервис склада — это большой набор полных end-to-end тестов через UI. Другой хочет почти без e2e, спустив всё на unit- и контрактные тесты. Вас просят предложить стратегию тестирования этих микросервисов. Спроектируйте ответ: как вы распределите покрытие по уровням, за что отвечает каждый уровень, где проверяются стыки между сервисами и как удержать e2e-ярус небольшим, не оставив рискованные межсервисные пути без тестов.
Спор о стратегии в команде: один инженер убеждён, что единственное надёжное покрытие для вашей системы оформления заказа — охватывающей сервис корзины, сервис цен, платёжный сервис и сервис склада — это большой набор полных end-to-end тестов через UI. Другой хочет почти без e2e, спустив всё на unit- и контрактные тесты. Вас просят предложить стратегию тестирования этих микросервисов. Спроектируйте ответ: как вы распределите покрытие по уровням, за что отвечает каждый уровень, где проверяются стыки между сервисами и как удержать e2e-ярус небольшим, не оставив рискованные межсервисные пути без тестов.
Ни одна крайность. Спустите основную массу вниз: unit-тесты на сервис для логики, контрактные тесты на каждом стыке, чтобы каждая пара оставалась совместимой без развёртывания всего, и тонкий e2e-ярус лишь по критическим путям — реальное оформление, отклонённый платёж, отсутствующий на складе товар. Каждый уровень владеет своим классом отказов, а пирамида держит обратную связь быстрой. Оставьте e2e для тех немногих путей, где риск — именно межсервисное взаимодействие, а не логика одного сервиса.
Типичные ошибки
- ✗Покрывать всё медленными flaky end-to-end тестами
- ✗Удалять контрактные тесты, считая зелёные unit'ы гарантией системы
- ✗Взвешивать каждый уровень поровну вместо формы пирамиды
Уточняющие вопросы
- →Какие пути оформления заслуживают редкий e2e-слот и почему именно они?
- →Как контрактные тесты покрывают стыки, что пропускает тонкий e2e-ярус?
MiddleДизайнЧастоОбзор устойчивости перед запуском. Страница товара тянет блок «рекомендуем вам» из downstream-сервиса рекомендаций и остатки со склада из сервиса инвентаря. Оба — отдельные развёртывания, которые могут тормозить, возвращать ошибки или падать целиком; кластер инвентаря многоузловой, и один узел может отказать посреди запроса. Команда хочет, чтобы страница оставалась полезной при деградации зависимости и не теряла закоммиченных данных, если один узел инвентаря умрёт. План тестирования на вас. Спроектируйте, как проверить graceful degradation: какие режимы отказа вы внедрите, что страница обязана делать, когда сервис рекомендаций упал против просто медленный, и как доказать, что отказ одного узла не теряет данных.
Обзор устойчивости перед запуском. Страница товара тянет блок «рекомендуем вам» из downstream-сервиса рекомендаций и остатки со склада из сервиса инвентаря. Оба — отдельные развёртывания, которые могут тормозить, возвращать ошибки или падать целиком; кластер инвентаря многоузловой, и один узел может отказать посреди запроса. Команда хочет, чтобы страница оставалась полезной при деградации зависимости и не теряла закоммиченных данных, если один узел инвентаря умрёт. План тестирования на вас. Спроектируйте, как проверить graceful degradation: какие режимы отказа вы внедрите, что страница обязана делать, когда сервис рекомендаций упал против просто медленный, и как доказать, что отказ одного узла не теряет данных.
Внедряйте режимы отказа осознанно — задержку, ошибки и полный отказ каждой зависимости, плюс убийство одного узла инвентаря посреди запроса. Проверяйте, что страница деградирует, а не ломается: при упавших рекомендациях она убирает этот блок и всё равно отрисовывает товар; когда сервис просто медленный, таймаут и fallback держат страницу в её бюджете, а не в подвисании. Для убийства узла убедитесь, что закоммиченная запись выживает, а запрос либо повторяется, либо чисто падает — без частичных или потерянных данных.
Типичные ошибки
- ✗Считать, что здоровый happy-path доказывает поведение при отказе
- ✗Блокировать всю страницу, когда упала одна некритичная зависимость
- ✗Не различать упавшую зависимость и просто медленную
Уточняющие вопросы
- →Почему медленную зависимость надо обрывать таймаутом, а не ждать?
- →Как внедрить отказ узла посреди запроса для проверки потерянных записей?
MiddleТеорияИногдаЧем consumer-driven контрактный тест на инструменте Pact отличается от end-to-end теста и когда выбирать каждый?
Чем consumer-driven контрактный тест на инструменте Pact отличается от end-to-end теста и когда выбирать каждый?
Контрактный тест на Pact фиксирует одну пару consumer–producer: consumer записывает ожидаемые запросы и ответы, producer их воспроизводит, и каждая сторона проверяется отдельно, без развёртывания обеих. End-to-end гоняет всю цепочку вживую — он ловит ошибки связки и потока данных, но медленный и flaky. Контрактные тесты указывают, какая сторона сломала контракт; e2e доказывает, что собранная система работает.
Типичные ошибки
- ✗Думать, что контрактный тест разворачивает всю систему, как e2e
- ✗Считать, что зелёный контракт делает end-to-end покрытие лишним
- ✗Забывать, что producer должен воспроизвести ожидания consumer'а для проверки
Уточняющие вопросы
- →Как Pact broker позволяет двум сторонам проверяться без совместного развёртывания?
- →Какой класс багов e2e всё ещё ловит, а контрактное тестирование упускает?
MiddleДизайнИногдаРазбор тикета: пользователь меняет отображаемое имя в профиле, видит новое имя на экране подтверждения, затем открывает свою публичную страницу и всё ещё видит старое — но лишь иногда, и это само исправляется примерно за минуту. Запись идёт в primary-базу, реплицирующуюся на read-реплики, а перед путём чтения стоит слой кэширования. Часть инженеров хочет завести это как баг потери данных. Вы разбираете тикет. Спроектируйте, как проверить, дефект это или ожидаемая согласованность в конечном счёте: что вы будете измерять, как задать допустимую границу устаревания и какое свидетельство превратит «кэш устарел» в настоящий баг, который стоит чинить.
Разбор тикета: пользователь меняет отображаемое имя в профиле, видит новое имя на экране подтверждения, затем открывает свою публичную страницу и всё ещё видит старое — но лишь иногда, и это само исправляется примерно за минуту. Запись идёт в primary-базу, реплицирующуюся на read-реплики, а перед путём чтения стоит слой кэширования. Часть инженеров хочет завести это как баг потери данных. Вы разбираете тикет. Спроектируйте, как проверить, дефект это или ожидаемая согласованность в конечном счёте: что вы будете измерять, как задать допустимую границу устаревания и какое свидетельство превратит «кэш устарел» в настоящий баг, который стоит чинить.
Сначала убедитесь, что запись долговечна, а не потеряна: прочитайте из primary, чтобы проверить, что она сохранилась, — это исключает потерю данных. Затем измерьте окно сходимости — за сколько все реплики и кэш начнут отдавать новое значение — и сравните с согласованной границей устаревания. Это ожидаемая согласованность в конечном счёте, если чтения сходятся в пределах границы; это баг, если окно превышает границу, устаревшее значение живёт дольше TTL кэша или значение не сходится вовсе.
Типичные ошибки
- ✗Звать ещё не видимую запись потерей данных, не проверив primary
- ✗Считать, что реплики и кэши обновляются синхронно с записью в primary
- ✗Не согласовывать границу устаревания, так что «устарело» не станет дефектом
Уточняющие вопросы
- →Как задать границу устаревания, отделяющую ожидаемое от сломанного?
- →Какое свидетельство отличает лаг репликации от застрявшей записи в кэше?
MiddleДизайнИногдаПродакшн-инцидент: пользователи real-time чата сообщают, что сообщения иногда приходят не по порядку, сообщение изредка появляется дважды, а двое, редактирующие один тред, несколько секунд видят разные списки сообщений, прежде чем те сойдутся. Транспорт — WebSocket-слой с fallback на polling, а клиенты переподключаются после коротких обрывов сети. На своём ноутбуке с одним открытым клиентом вы это не воспроизводите. Спроектируйте, как тестировать синхронизацию состояния в реальном времени, чтобы воспроизвести и локализовать эти симптомы: какие условия вы создадите, что будете проверять про порядок и доставку и как отделить настоящий баг синхронизации от ожидаемой сходимости в конечном счёте.
Продакшн-инцидент: пользователи real-time чата сообщают, что сообщения иногда приходят не по порядку, сообщение изредка появляется дважды, а двое, редактирующие один тред, несколько секунд видят разные списки сообщений, прежде чем те сойдутся. Транспорт — WebSocket-слой с fallback на polling, а клиенты переподключаются после коротких обрывов сети. На своём ноутбуке с одним открытым клиентом вы это не воспроизводите. Спроектируйте, как тестировать синхронизацию состояния в реальном времени, чтобы воспроизвести и локализовать эти симптомы: какие условия вы создадите, что будете проверять про порядок и доставку и как отделить настоящий баг синхронизации от ожидаемой сходимости в конечном счёте.
Воспроизводите на нескольких клиентах, а не одном: сценарьте два и более сеанса, вставляйте переподключения и обрывы сети и чередуйте отправки, чтобы всплыли гонки. Проверяйте порядок в пределах одного отправителя, ровно однократный показ после переподключения и что каждый клиент сходится к одному финальному списку в ограниченном окне. Отделяйте настоящий баг — стойкое расхождение или потерю и перестановку сообщений после сходимости — от ожидаемого временного лага, пока состояние ещё устаканивается.
Типичные ошибки
- ✗Пытаться воспроизвести баг параллелизма одним клиентом
- ✗Считать дефектом любое временное различие между клиентами
- ✗Полагать, что порядок TCP в одном сокете гарантирует порядок у всех клиентов
Уточняющие вопросы
- →Как отличить стойкое расхождение от нормального окна устаканивания?
- →Почему обрыв связи посреди диалога вскрывает повторную доставку?
SeniorДизайнИногдаВы — senior QA, проектирующий стратегию тестирования оформления заказа, интегрированного со сторонним платёжным шлюзом, который вам не подконтролен. Двигать реальные деньги нельзя, а вендор берёт плату за каждую живую транзакцию, поэтому набор, бьющий по проду, исключён. Шлюз возвращает часть результатов синхронно — авторизация принята или отклонена — и подтверждает расчёт асинхронно через webhook, который может прийти спустя минуты, продублироваться или прийти не по порядку. Нужно покрыть всю матрицу — успешный платёж, отклонение, таймаут, частичный сбой, возврат и дубль webhook — без реальных списаний. Спроектируйте стратегию: как прогонять шлюз без живых транзакций, как удерживать ваш тестовый двойник верным реальному контракту со временем и как проверять асинхронный путь расчёта.
Вы — senior QA, проектирующий стратегию тестирования оформления заказа, интегрированного со сторонним платёжным шлюзом, который вам не подконтролен. Двигать реальные деньги нельзя, а вендор берёт плату за каждую живую транзакцию, поэтому набор, бьющий по проду, исключён. Шлюз возвращает часть результатов синхронно — авторизация принята или отклонена — и подтверждает расчёт асинхронно через webhook, который может прийти спустя минуты, продублироваться или прийти не по порядку. Нужно покрыть всю матрицу — успешный платёж, отклонение, таймаут, частичный сбой, возврат и дубль webhook — без реальных списаний. Спроектируйте стратегию: как прогонять шлюз без живых транзакций, как удерживать ваш тестовый двойник верным реальному контракту со временем и как проверять асинхронный путь расчёта.
Тестируйте против sandbox вендора плюс контрактно-проверенного stub, а не прода: sandbox прогоняет реальное поведение протокола с тестовыми картами под каждый исход, а stub даёт детерминированный контроль над отклонениями, таймаутами и дублирующимися или пришедшими не по порядку webhook. Держите двойник верным контрактным тестом против вендора, чтобы он не разошёлся молча. Проверяйте расчёт, утверждая конечное состояние, к которому приходит ваш обработчик webhook — идемпотентно и независимо от порядка — никогда по фиксированному ожиданию.
Типичные ошибки
- ✗Бить по живому API вендора, полагаясь на возвраты для отмены стоимости
- ✗Верить рукописному stub без контрактного теста на дрейф API
- ✗Проверять асинхронный расчёт фиксированным sleep вместо конечного состояния
Уточняющие вопросы
- →Как контрактный тест против вендора удерживает ваш stub от дрейфа?
- →Как проверить возврат и дубль webhook без реальных денег?
JuniorТеорияРедкоЧто ломается только на стыке двух сервисов и что ловит интеграционный тест, чего не поймает unit-тест?
Что ломается только на стыке двух сервисов и что ловит интеграционный тест, чего не поймает unit-тест?
Стык несёт контракт — имена и типы полей, сериализацию, коды статусов, таймауты, авторизацию и расхождение версий. Unit-тест подменяет вторую сторону заглушкой и лишь подтверждает вашу же догадку; интеграционный тест делает настоящий вызов и ловит момент, когда догадка неверна.
Типичные ошибки
- ✗Считать, что заглушка доказывает контракт, тогда как она лишь кодирует вашу догадку
- ✗Списывать любой отказ на стыке на инфраструктуру, которую заметит эксплуатация
- ✗Думать, что интеграционный тест — медленный unit-тест без своего класса отказов
Уточняющие вопросы
- →Почему полностью зелёный unit-набор всё равно выпускает сломанную интеграцию?
- →Какие отказы на стыке интеграционный тест всё равно не поймает?