Привет, меня зовут Андрей и я автоматизатор (остальные в кругу хлопают в знак сочувствия) в hh.ru. В статье расскажу, как мы ввели карантин автотестов, повысив стабильность релизов и скорость доставки.

Предпосылки

У нас много тестов: стандартный прогон (как и самые крупные релизы, например, монолитов) включает 2300 тестовых классов, которые содержат несколько тестовых методов, не считая датапровайдеров (мы пользуемся TestNG, в JUnit это @ParameterizedTest). К сожалению, не все тесты проходят с первого раза — и даже со второго, третьего или N-го (у нас есть встроенные адаптивные ретраи, но эта тема для отдельной статьи).

Ретрай в нашем случае — это автоматический перезапуск упавших тестовых методов класса во время текущего прогона автотестов. Тест, который упал, но был отправлен на перезапуск, сохраняется в статистике запусков с результатом retry.

Некоторые из этих тестов не проходят с первого раза довольно часто, т.е. их падения связаны с нестабильностью, а не с выпускаемыми изменениями.

Падения тестов мы не любим — во время релиза это означает остановку автоматики (наши внутренние системы CI/CD) и ожидание прихода человеков, которые какое-то время разбираются в причинах падения: зовут коллег, которые скажут, что «тест просто нестабильный, перезапусти его несколько раз», или дадут добро на скип — пропуск упавших тестов, когда автор релиза самостоятельно двигает задачу в Jira в следующий статус.

Релизов у нас — несколько десятков в день, так что остановка тормозит не только текущий релиз, но и те, что в очереди за ним. Такое мы любим еще меньше.

Решение

Мы решили попробовать несколько видов карантина для самых нестабильных тестов.

Здесь и далее под тестом мы будем подразумевать тестовый класс, чтобы сэкономить буквы и когнитивную нагрузку.

Карантин в статье — это список тестов, которые наш тестовый фреймворк (TestNG + собственный раннер) не будет запускать, даже если они подпадают под условия для запуска.

Технически хранилище списка тестов в карантине устроено несложно: грубо говоря, это таблица в БД + HTTP API над ней, куда ходит наш раннер за списком тестов. В таблице есть такие поля, как: имя тестового класса, дата добавления в карантин, дата окончания карантина, имя добавившего тест в карантин и т. д.

Самое интересное — это то, как тесты попадают в карантин. Мы придумали три варианта:

1. Ручной карантин

Самый понятный вид карантина: если видим, что тест больше не проходит (обычно — в 100% случаев), то мы вручную добавляем его в карантин, чтобы релизы больше не останавливались в ожидании ручного скипа тестов.

Отключать тесты можно было и раньше — через правки в коде. Но это означало создание PR (pull request), ожидание аппрува PR, мерж — и всё то же самое некоторое время спустя для включения теста обратно.

Также при добавлении теста мы даём выбрать срок карантина:

  • до завтра — если знаем, что фикс уже есть и вечером вместе с релизом он попадёт в мастер

  • навсегда (то есть до ручного удаления теста из карантина) — если понимаем, что на исправление ситуации может понадобиться больше времени.

Технически за это отвечает поле expiration_date в БД: по нему мы фильтруем результаты внутри API, отвечая на запросы списка текущих тестов в карантине.

2. Полуавтоматический карантин

Если в результате повторного прогона упавших автотестов на релизе упало менее N* тестов, их можно вручную отправить на контрольный перепрогон на этаноловом эталонном стенде — стенде, где установлены актуальные версии сервисов, так как на релизе как минимум одна версия отличается и тесты могут падать по делу. На эталонном стенде каждый тест запускается K* раз. Если падение тестов на контрольном прогоне продолжается, они автоматически добавляются в карантин до следующего дня, а в командные чаты владельцев тестов отправляются уведомления.

*N и K мы выбирали эмпирически так:

  • падение более N тестов уже вызывало подозрение на массовую проблему, не связанную с конкретным тестом

  • падение теста менее K раз ещё вызывало подозрение на невезучесть теста или массовые проблемы

Нюансы

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

3. Автоматический карантин на основе статистики автотестов

Это самый интересный и сложный из наших типов карантина. Мы разработали ряд критериев, по которым считаем, что тесту пора на покой в карантин — или как минимум стабилизироваться.

Все ходы результаты запуска тестов у нас записаны в БД (в разрезе тестовых классов). Раз в день бот (Python + pandas) на основе этой статистики добавляет тестовые классы в перманентный карантин и заводит/обновляет задачи на стабилизацию или ускорение теста (есть и такой критерий).

Например, если медианная стабильность теста за 7 дней без учёта ретраев меньше 70%, мы заводим задачу со средним приоритетом на стабилизацию и добавляем тест в карантин. 

Для нас важнее количество падений, чем количество ретраев — любое падение приводит к ожиданию (автоматикой) ручного разбора тестового прогона на релизе.

Есть и более мягкие критерии, по которым мы заводим задачи на стабилизацию теста без его добавления в карантин. Например, если медианная стабильность за 7 дней без учета ретраев находится в диапазоне 85-95%.

Но такие задачи выглядят как техдолг, плавают как техдолг и крякают как техдолг — а его, как известно, можно копить годами (утрированно). Поэтому мы разработали мотивационные механики:

  • Для каждого из приоритетов задач мы выбрали SLA (Service Level Agreement). В данном случае —- сколько дней задача может находиться в каждом из приоритетов, при нарушении которого приоритет по задаче поднимается и начинается новый отсчет соответствующего SLA

  • При нарушении SLA по задаче в максимальном приоритете тест добавляется в карантин

  • Ответственность за баги, пропущенные таким тестом, несёт команда-владелец теста

Нюансы

  • Некоторые тесты для нас слишком критичные, чтобы их пропускать — в настройки бота мы добавили список таких тестов, чтобы никогда не добавлять их в карантин.

  • Иногда случаются массовые проблемы — в настройках бота также есть список ошибок, которые не учитываются при анализе стабильности (текст ошибки мы тоже храним в таблице с результатами запуска тестов)

Удаление из карантина

Мы решили не делать автоматическое удаление из карантина. Иначе может возникнуть желание «переждать бурю», ничего не делая с тестом — вдруг само рассосётся. Даже если тест нестабилен сам по себе, а не из-за каких-то массовых проблем со стендами.

Зато в планах — регулярная проверка карантина для следующих случаев:

  • тест слишком долго находится в карантине

  • последняя заведенная задача на стабилизацию теста уже закрыта, а он всё ещё находится в карантине — иногда коллеги всё чинят, закрывают задачу, но забывают удалить тест из карантина

Как понять, что тест стабилизирован

В нашем тестовом фреймворке есть специальный режим для проверки стабильности: запускаются все тесты, а параллельно в цикле запускается проверяемый тест. В конце смотрим, сколько раз он успешно прошёл и сколько упал.

Тест считается стабилизированным, если:

  • не было ни одного падения

  • было небольшое количество падений, которые не связаны с функциональностью, которую он проверяет — например, сервис не выдержал нагрузки и ответил кодом 5хх вместо главной страницы

А вот теперь слайды ©

Скорость починки тестов

Автоматика по заведению задач у нас была и раньше — тогда она просто создавалась для тестов с совсем плачевной статистикой. Но коллеги не всегда оперативно брались за такие задачи — самым старым из них (задачам) могло быть несколько месяцев.

С введением SLA и автодобавлением в карантин при просрочке ситуация заметно улучшилась: число открываемых и закрываемых задач примерно одинаковое (с поправкой на майские праздники):

Соотношение тестовых прогонов без ошибок к общему числу прогонов

Как видим, после запуска доля успешных прогонов (больше — лучше) заметно выросла
Как видим, после запуска доля успешных прогонов (больше — лучше) заметно выросла

Итоги

Благодаря автоматическому карантину и заведению задач на починку тестов с контролем SLA мы ускорили работы по стабилизации тестов и увеличили среднее количество прогонов, проходящих с первого раза — а значит, ускорили доставку наших изменений пользователям.

Если у вас есть нестабильные тесты и вы хотите себе такое же, то следует:

  • разработать критерии попадания в карантин и выхода из него

  • определиться с SLA на починку тестов

  • решить организационные вопросы: договориться с командами о своевременном реагировании на задачи и т. д. 

Вот и всё. С радостью отвечу на все вопросы (в том числе технические) в комментариях.

P.S. Наши мобильные коллеги также рассказали о карантине автотестов в iOS.

Комментарии (0)