Красное не мёржим: как мы внедрили e2e тесты в разработку
Существует простое правило: чем раньше найден баг, тем он дешевле.
Представим проект, где тестирование есть только на production. Баг находят уже после релиза. Фича, которая приносит деньги, становится недоступна. Мы теряем прибыль, тратим время на дебаг, фикс и доставку — а вместе с тем время на post mortem и многочисленные созвоны по инциденту.
Сдвигаем тестирование на один пункт и ставим его перед релизом. Результат — не релизим кандидата с проблемами и исправляем все заранее. Фича не сломана. Поток денег и клиентов не прерывается. Все круто, но есть нюанс.
Если на этапе тестирования находим критичный баг, то процесс разработки начинается сначала. Мы тратим время на воспроизведение, обсуждение, фикс, ретест, доставку. Релиз проведен. Все функции в порядке. Поток денег не прерывается.
Остается проблема управления временем и соответствия срокам релиза.
Частые правки Релиз кандидата отодвигают доставку и увеличивают time‑to‑market. Что негативно сказывается на жизненных метриках продукта.
Можно ли как‑то обезопасить себя от таких проблем и повысить уверенность в своем коде? Вот тут на сцену выходит виновник данной статьи.
Что такое Shift left
Shift left — подход, при котором тестирование сдвигается влево по этапам разработки продукта. Он необходим для раннего обнаружения проблем и их своевременного решения. Причем левую границу можно ставить настолько влево, насколько захочется: можно даже приставить к стейкхолдерам QA, задающего каверзные вопросы.
На каждом этапе можно и нужно проводить тестирование: требований, пользовательских сценариев, дизайна. Это всё ручные, визуальные и логические проверки.
Из пирамиды тестирования мы как QA можем использовать только три вида тестирования:
-
API тесты;
-
e2e тесты;
-
ручное тестирование.
Unit‑тесты обычно пишут сами разработчики.
В данной статье основное внимание уделено Shift left подходу именно в e2e автоматизации тестирования мобильного приложения.
Контекст проекта:
-
мобильное приложение на React Native, e2e тесты (Detox) гоняются на эмуляторах (android) и симуляторах (iOS), локально и на CI (TeamCity);
-
тестовый framework лежит в одном репозитории с приложением;
-
AQA сам назначает
testIDи пишет инфраструктуру для тестов; -
тесты прогоняются на Staging среде,
-
Время прогонов — раз в сутки ночью и на раз в неделю на релиз кандидате;
-
Время прогона на одной платформе ~ 50–60 минут;
-
AQA один на проекте и не состоит в продуктовой команде.
Кейс применим к разработке когда код тестов лежит в репозитории проекта.
Процесс без Shift left
Стандартная картина выглядит примерно так. Функционал разработали. Провели ручное приемочное тестирование. Если все ок — замёржили в main. Добавили тест‑кейсы в регресс. И теперь на каждом релизе проходим те же самые проверки. Количество feature растёт, количество ручных тест‑кейсов растёт, время регресса растёт.
Возникает логичный запрос — снижать время тестирования релизов за счёт автоматизации. Начинаем писать тесты на уже существующий функционал. За какое‑то время пишем тесты, покрываем сценарии и сокращаем время на тестирование релизов.
Но разработка не стоит на месте. Пока мы пишем тесты на существующий функционал, три новых уже могут попасть в релиз. Время на тестирование релиза снова увеличивается. Растёт технический долг.
Потом происходит редизайн — и большую часть тестов нужно переписывать.
Или кто‑то мержит код, ломающий инфраструктуру для тестов, и обнаруживается это только на релизе. Ручные тестировщики в шоке: неужели нужно вручную проходить все автотесты? AQA в диком стрессе и второпях гуляет по коммитам и смотрит, что могло сломать тесты.
Кейс. Один commit добавил hook, вызываемый раз в 500 мс. Для пользователя это незаметно, а Detox выполняет следующее действие теста только когда приложение стало idle — он следит за таймерами, анимациями и JS‑потоком RN. Бесконечный таймер означает, что idle не наступит никогда: каждый шаг упирается в таймаут, прогон просто зависает.
Пропустить такой коммит легко — при ручном тестировании это незаметно. Но их становится два, пять, десять, и вместе они постоянно будят JS‑поток: просадка перфоманса, лишний трафик, разряд батареи. Здесь «слабости» фреймворка сработали как ранний детектор проблемы, которую иначе заметили бы через полгода по метрикам.
Недостатки такого подхода:
-
Баги ловим либо на ночных прогонах, либо на release candidate, либо уже в production. Тратим время на фикс и доставку
-
Тестовый прогон может сломаться из‑за одного коммита. У AQA нет контекста, что именно могло его сломать.
-
Растёт технический долг по покрытию тестами, потому что AQA только и делает, что чинит тесты, вместо написания новых.
-
Контекст функционала быстро теряется, и фикс занимает заметно больше времени, чем занял бы сразу после написания кода.
Решение
Главный вопрос звучит так: «Зачем откладывать возможные проблемы на потом, если можно сейчас все спокойно проверить и починить?»
Решение простое на словах:
-
чинить тесты в том же PR, в котором их сломали
-
блокировать merge с красными тестами
-
писать тесты вместе с feature (это в идеале)
Сдвиг e2e тестирования влево всего лишь на одну позицию — но сколько нужно подготовки и ресурсов для успешного внедрения…
Проблемы внедрения
Коротким списком проблемы выглядят так:
-
Удлинённый процесс разработки
-
Падающие тесты
-
Очереди на CI
-
Сомнения со стороны разработчиков
-
Паника перед релизом
Рассмотрим каждую проблему вместе со своим решением.
1. Удлинённый процесс разработки
Процесс разработки увеличится — но насколько, зависит от каждого конкретного разработчика.
Необходимо грамотно подготовить документацию по установке окружения, запуску тестов локально и на CI, познакомить с ней разработчиков. Показать покрытие, как писать и дебажить тесты. В общем — потратить приличное количество времени на объяснение деталей и совместный debug.
2. Падающие тесты
Тесты в main должны быть зеленые. Pass rate — 100%.
Но некоторые тесты могут быть flaky. Их надо чинить. Необходимо убедиться, что они не зависят от:
-
других тестов;
-
действий других пользователей
-
времени суток и часовых поясов;
-
денег на счету и прочих внешних состояний.
Тестовые данные должны быть стабильными и предсказуемыми. Если тесты падают по другим причинам — инфраструктура, нестабильность framework, эмуляторы — эти причины нужно устранить по максимуму. Тут поможет только debug и решение проблем одна за одной.
В реальности добиться стабильного стопроцентного pass rate в е2е тестах сложно. Тут может помочь механизм retry. Мой совет — не ставьте больше одного. Если тест упал два раза подряд — с ним нужно разобраться.
Только когда AQA уверен, что main стабилен — можно внедрять данный quality gate.
3. Очереди на CI
На момент внедрения у нас уже много тестов и полный прогон занимает примерно час. Прогонять часовые тесты на каждый PR звучит отлично… если бы на проекте был всего один разработчик и он делает один PR в неделю. Когда есть целая команда frontend‑разработчиков — картина куда страшнее.
Решать проблему можно с нескольких сторон.
Первое — расширить парк машин. Можно просто арендовать много удалённых машин и гонять на них сборки и тесты. Но в какой‑то момент многие из них будут простаивать, и это будет просто потеря денег. Но это крайность. Важно расшириться и грамотно распределять нагрузку.
Второе — не прогонять тесты много раз на один PR. Мы пробовали запускать на каждый commit и push. Просто ужас. Методом проб и ошибок пришли к тому, что разработчик сам запускает тесты, когда понимает, что запушил всё, что хотел, и готов к приемочному тестированию и e2e тестам. До этого момента github check будет в статусе pending (с таким статусом тоже нельзя смержить)
Третье — внедрить параллелизацию. Тут тоже многое зависит от железа. Нам удалось сократить время прогона в три раза — получилось 20 минут на платформу.
Четвёртое — запускать только НУЖНЫЕ тесты. Тут на сцену выходит самый сложный и ресурсно затратный precondition — impact analysis. Коротко говоря, это job, которая в рамках условных Danger checks и показывает, какие модули были задействованы в данном PR. Job с тестами берёт этот список, матчит его с имеющимися тестами и запускает только те, у которых совпали теги.
Некоторые правила запуска. Если изменения только в нативной части приложения — запускаем только затронутую платформу. Если изменения в package.json, то запускаем все тесты. На безобидные коммиты вообще не запускаем тесты. Например на правки документации или скиллы для агентов.
4. Сомнения со стороны разработчиков
Люди сразу воспринимают дополнительную работу, мягко говоря, негативно. Поэтому очень важно донести идею до всех — почему мы все от этого выиграем. Сразу скажу: понимания от всех не будет.
Тут нужно действовать хитро. Находим себе сообщника — того, кто запустил тесты, нашёл у себя баг и увидел воочию их пользу. Теперь он в вашей Shift left команде и будет поддерживать вас и вашу идею на общих встречах. С каждым новым участником количество отрицающих будет уменьшаться, и процесс вскоре окончательно приживется.
5. Паника перед релизом
Это следствие всех предыдущих пунктов, поэтому разберу его подробнее.
Рассмотрим кейс, который был слишком часто на моей практике( Shift left не внедрен). Прямо перед сборкой релиз кандидата происходят последние мержи. Множество коммитов, на которых не прогонялись тесты (последний прогон был ночью). Релизный прогон e2e тестов падает. Полностью. 200+ проверок на каждую платформу. Для manual QA это значит, что регресс продлится все 10 часов вместо 3. AQA в поте лица перебирает коммиты в поисках виновника. Если это не успеть быстро локализовать, то релиз приходится сдвигать. Ну дальше уже сами знаете. И бизнес, и инженеры не в восторге от всего этого.
Как Shift left с этим справляется. Данный подход повышает уверенность в своем коде. И переносит такие проблемы влево по процессу — туда, где они стоят дёшево и спокойно решаются:
-
Статус release candidate известен заранее. Если main всегда зелёный, то к моменту сборки релиза он уже проверен — а не выясняется при сборке релиз кандидата.
-
Каждое падение локализовано. Тесты упали на конкретном PR, автор рядом, контекст свежий. Даже если разработчик не в ресурсе поправить, AQA может погрузиться в контекст и сделать все самому.
-
Нет необходимости вручную проверять упавшие автотесты. падений на релизном прогоне становится единицы, и каждое уже известны.
Правила реализации:
-
«Красные тесты = merge blocked». На любой платформе. Без исключений и на уровне github rules. Если сделать этот quality gate опциональным, то внимания к падениям будет примерно нисколько.
-
Каждое падение в main расследуется как маленький инцидент. Задаем вопрос: «почему это попало в main и что сделать, чтобы не повторилось».
-
Все изменения в e2e тестах — только через код‑ревью AQA. AQA как обязательный ревьюер гарантирует, что это не пройдёт мимо.
-
Полный прогон по расписанию остаётся. Impact analysis запускает только часть тестов, поэтому ночной полный прогон никуда не девается.
В день включения блокирования PR советую выспаться и не отходить от рабочего места дольше, чем на минуту.
Полезные практики
-
Внедрить screenshot‑тесты. Если во время редизайна тесты поломались, их можно починить запуском тестов с флагом обновления baseline‑скриншотов — вместо правки десятков селекторов. Да и в целом их писать легче и приятнее.
-
Сделать скрипт для установки всех тестовых зависимостей и эмуляторов, чтобы одной командой подготовить окружение перед запуском тестов. Сюда же — удобный запуск тестов через shortcut с указанием тега. Тогда onboarding разработчиков в e2e‑тесты пройдёт молниеносно.
-
Добавьте AQA в CODEOWNERS тестовой директории. Если будут изменения в тестах или скриншотах — вас github автоматически назначит обязательным ревьюером, и вы это не пропустите. Я так не пропустил, как разработчик пытался закомментировать упавший тест:)
-
Используйте парное программирование, чтобы починить тесты прямо в PR разработчика, если там что‑то, в чём разбираетесь только вы, или требуется глубокий debug. Да, это занимает время, но с контекстом это намного проще и быстрее.
Заключение
Shift left не спасает от багов, но находит их гораздо раньше.
Баги, которые раньше долетали до release candidate или до production, теперь ловятся до merge — feature не попадает в main, если тесты красные. Критичных багов в production стало примерно вдвое меньше — по задачам в Jira за сопоставимые полгода до и после. Цифра грубая: за это время менялся и состав команды, и частота релизов, так что списывать всё на shift left я бы не стал. Дашборд «Bugs found by automation» пополняется каждую неделю, что показывает результат.
Прогон e2e‑тестов в PR на одной платформе занимал 1 час → с параллелизацией — 20 минут → c impact analysis — 12 минут (в среднем) — разработчик получает обратную связь, еще не переключившись на следующую задачу.
Релизы проходят гораздо спокойнее, особенно для QA команды.
Команда, которая изначально встретила подход в штыки, постепенно перешла на сторону Shift left: несколько разработчиков сами начали писать тесты на свои фичи.
P. S. Вся информация в статье — мой личный опыт работы в стартапе. Процесс перехода и отладки занял около 4 месяцев. Но результат того стоил, и это не только мое мнение. Надеюсь, этот опыт поможет вам в ваших процессах и компаниях. Удачи!
Автор: eilinwis

