Красные тесты — это неприятно, но есть кое-что похуже — тесты, которые то красные, то зеленые. С флаки-тестами сталкивается каждый продукт. И чем больше вы тестируете, тем больше мучительных выяснений, какие тесты — флаки, а какие — нет.
Меня зовут Александр Милов, я отвечаю за тестирование в Platform V Pangolin — это основная СУБД в Сбере, специальная сборка PostgreSQL, созданная для хранения и обработки данных в высоконагруженных приложениях.
Мы начали делать Pangolin в 2019 году. Долгое время флаки-тесты анализировались вручную, а информация о них передавалась от тестировщика к тестировщику каждую неделю. По мере роста числа тестов это перестало быть возможным (одно дело — отслеживать так 5–10 тестов, другое — 30–50). Сейчас мы запускаем 5000 тестов, у нас более четырех основных конфигураций развертываний системы, пять поддерживаемых ОС, включая 8 версий этих ОС, а также большое количество (порядка 160) специальных quality gates с различными параметрами запуска. Да, и помимо регресса после установки продукта, мы тестируем его и после обновления и отката…
Как в таких объемах отслеживать флаки-тесты? Есть готовые инструменты, как платные, так и бесплатные, однако нам было нужно решение, которое позволяло бы не только проводить анализ в разрезе всех вышеназванных параметров, но и находить закономерности. Мало выяснить, что у тебя падает 100 тестов, хочется увидеть, что эти 100 тестов падают только на одной ОС и только с конкретным параметром установки.
Поначалу мы боролись с флаки-тестами «в лоб» — брали и пытались исправить. Как правило, все сводилось к «А давай сделаем задержку?», «А что, если сделать дополнительный рестарт?». Но так или иначе нам нужен был системный подход.
Как устроен инструмент?
Для начала покажу и кратко расскажу, как выглядит наше решение со стороны фронтенда, и пойдем разбирать изнанку.
На скрине ниже мы выбираем текущую версию, имя теста или сьюта, а также интервал времени, за который берется статистика. Красный — это падения, а зеленый — успешные прохождения тестов на определенную дату. По каждому столбцу можно настроить сортировку, скрыть/вывести дополнительную информацию или колонки.
Кликнув на тест, мы перейдем в детальную статистику по прохождению. Нажав на один из красных или зеленых квадратов, получим более подробную информацию о прогоне, где он запускался. Здесь мы еще можем посмотреть лог упавшего теста, а также строчку в исходном коде, где он упал. Итого: путем пары кликов мы видим информацию о падении любого теста за 30 дней (можно выбрать любой отрезок времени, в зависимости от того, сколько хранятся данные).
Это лишь маленькая часть фронта нашего сервиса. Имея данные о прогонах, а также кэшируемые логи, мы можем экспериментировать с любой статистикой.
Откуда же берутся эти данные?
Тут все просто — результаты каждого теста пишутся в БД, которая управляется нашей же СУБД Pangolin. С точки зрения кода все зависит от фреймворка, которым вы пользуетесь. С нашей стороны — это unitetest, поэтому нам не составило труда в конце каждого теста писать информацию о результатах в таблицу в БД.
Имея все данные в едином представлении, мы их структурируем в зависимости от версии и от того, как именно были запущены тесты.
Почему это важно? Ведь достаточно сделать простой WHERE, и все будет работать.
Тут-то и возникают подводные камни.
Первое. Для анализа флаки мы должны руководствоваться только тестами, запускавшимися с ветки develop (референсная ветка, с которой отгружается релиз). Тесты, запущенные с веток, отличных от develop, не должны учитываться в подсчете флаки — на этих ветках могут быть ошибки и в самих автотестах.
Второе. Мы не должны слепо смотреть только на ветку develop-автотестов. Нам нужно понимать, что те же тесты с ветки develop могли быть запущены на кастомной ветке разработчика.
В итоге для подсчета флаки мы должны учитывать только те прогоны, которые были запущены на актуальном develop-билде и на develop-ветке автотестов.
Для этого мы используем ежедневный регресс — достаточно лишь прокинуть метку в базу.
В основное представление (таблицу) мы пишем главную информацию о тесте/прогоне: имя теста, время запуска, длительность, принадлежность сьюту (об этом подробнее распишу ниже), ОС, с какими фичами установлен или был бы обновлен стенд, номер сборки в дженкинсе и, само собой, результат выполнения теста.
На прошлом скрине вы видели два столбца: Done и Done 2w. Это сделано для того, чтобы в таблице присутствовали только те тесты, которые флакают на текущий момент.
Приведу пример. У нас упал флаки тест, который падает чаще всего, Тестировщик проанализировал его, изучил причину падения и все исправил. После этого исправления тест перестал падать и проходит все время успешно (как на скрине ниже):
Но если смотреть по всему времени выполнения теста, то он никогда не пропадет из статистики, ведь его процент прохождения за все время не будет равен 100%.
Поэтому мы ввели отсечку в данных. Мы понимаем, что это флаки-тест, если он хотя бы раз упал в течение определенного периода (в нашем случае — две недели). Как итог: если пофиксенный тест не упадет за следующие две недели, то он автоматом пропадет из статистики.
Так, на каждую версию продукта мы создаем два представления. Первое представление — за все время существования версии, а второе — за конкретный период.
С точки зрения представлений — мы выбрали Materialized View.
Ниже код представления с выборкой данных за последние 14 дней. В качестве флага прогонов в данном случае используется «branch_name ~~ '%6.2.0/develop_night%'»
Имея процент выполнения тестов (как общий, так и за период), нам остается лишь объединить информацию из разных представлений и получить итоговую таблицу по флаки-тестам, на основании которой уже отображается информация на фронте.
Как все это управляется?
Раз в день (можно и чаще, но особой необходимости у нас не было) у нас обновляются через REFRESH все Materialized Views.
Сделано все это с использованием расширения pg_cron.
Помимо обновления представлений, мы удаляем старые данные о прохождении тестов. В данном случае мы также разделяем тесты на develop/иные. С точки зрения не develop-веток сохраняем данные лишь за последние 30 дней. А вот с точки зрения develop-тестов — сохраняем результаты не за последнее время, а за последние 45 дней, начиная с крайнего запуска.
При создании новой версии продукта мы запускаем процедуру, которая автоматически добавляет все необходимые представления для новой версии.
Мы не забыли и о создании бэкапов — уже с помощью linux-утилиты cron.
В основную таблицу мы добавляем не только результаты тестов, но и результаты сьютов. Это сделано для того, чтобы считать общее время выполнения сьюта (ведь порой подготовка тестов SetUpClass выполняется в разы дольше, чем сами тесты). Имея среднее время выполнения каждого сьюта, мы можем прогнозировать время прогона, исходя из количества сьютов, и распараллеливать прогоны, исходя из среднего времени их выполнения. Но это уже другая история о том, как мы существенно сократили время выполнения наших тестов. Выберусь из релизов — напишу продолжение. Или, что вероятнее, побыстрее я расскажу об этом в сообществе нашей команды, кому интересно, присоединяйтесь.
Что же мы в итоге получили?
Прежде всего, удобство. Инженеру теперь достаточно зайти на страницу, выбрать тест, который чаще всего падает, узнать о нем все подробности и сразу для отладки поставить себе тот стенд и на той ОС, где он чаще всего падает.
Кроме того, зная, что тест — флаки, мы можем доработать код запуска автотестов и перезапускать данные этого теста сразу при первоначальном прогоне.
Глядя на статистику, мы также вывели и закономерность. Определенные тесты падают лишь на определенных конфигурациях развертывания. На основании этих результатов, правки были уже не в автотестах, а в исходном коде продукта.
В дальнейшем мы планируем строить диаграммы сгорания флаки-тестов по каждой из фич, а также применять эту информацию при просмотре финальных отчетов о прогонах.
Спасибо за внимание! Готов ответить на вопросы о решении в комментариях.