Как мы ускорили автоматизированное тестирование в большом проекте

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Мы делаем масштабные приложения на высококонкурентном рынке. Чем выше скорость обновлений и внедрений новых фич, тем больше зарабатывает компания и её сотрудники. Поэтому мы постоянно оптимизируем время прохождения автотестов. Изначально автоматизированное тестирование одного приложения занимало 16 часов. Мы уменьшили это показатель до 8 часов. В статье рассказываем, какие практические шаги сделали, чтобы добиться такого результата. 


Проблема: тратим много времени на автоматизированное тестирование

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

Изначально прохождение 2000 E2E автотестов занимало у нас 16 часов или 2 рабочих дня. В среднем происходило 8 запусков на одну публикацию, а это значит мы тратили на один автотест 2 часа. 

Мы задались целью сократить этот показатель на 50%.

Решение: ускорить время прохождения автотестов 

В интернете можно найти разные советы по ускорению тестов, 70% из них относятся к «уберите thread.sleep», «уберите зависимости между тестами», «замените ui тесты тестами api» и, наконец, «давайте начнем mock’ать». Такие рекомендации не всегда работают для больших проектов, которые хотят тестировать всё приложение, а не отдельные его части. 

Мы решили пойти своим путём. Команда проанализировала текущие запуски и выделила основные направления для будущих работ. А именно: 

  1. Оптимизировать тестовый фреймворк

  2. Увеличить стабильность автотестов

  3. Оптимизировать очередность автотестов в тестовом запуске

  4. Увеличить параллелизации автотестов

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

Этап 1. Оптимизация тестового фреймворка

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

В нашей команде автоматизации сформировалась высокая культура программирования. Мы не используем thread.sleep в коде, только явные ожидания. Наши тесты не зависят от общих ресурсов: один тест не ждет пока другой освободит общий ресурс.

Вот несколько правил, которые мы сформировали, чтобы добиться оптимального фреймворка:

  • Использовать только явные ожидания;

  • Не использовать зависимости;

  • Подготавливать данные перед тестом и использовать общие ресурсы, которые не влияют на прохождение тестов;

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


Этап 2. Увеличение стабильности автотестов

E2E selenium тесты нестабильны, наши — не исключение. Если автотест упал один раз, то мы по умолчанию его перезапускаем. При повторном падении считаем, что есть проблема, тест больше не мучаем.

Каждый перезапуск увеличивает продолжительность всего тестового запуска, поэтому мы решили работать над стабильностью и выявлять причины такого поведения. Прикрутили Kibana, чтобы логировать каждый тест. В лог записываем  уникальный номер, время старта и завершения, окружение запуска и результат. Из Kibana получили статистику, где посмотрели стабильность автотестов. После отсортировали автотесты от наименее к наиболее стабильному. Каждую неделю мы собираем статистику, делаем выборку по всем тестам и получаем вероятность прохождения как passed_count/(passed_count + failed_count), сортируем по этому числу и начинаем разбираться в причинах. 

В команде есть специальный процесс, в рамках которого мы разбираем автотесты, заводим баги на разработку или на самих себя.

Когда мы только начинали этот процесс, худший тест имел вероятность прохождения всего 0,1. Сейчас это показатель поднялся до 0,67. Таким образом мы уменьшили количество перезапусков автотестов в тестовом запуске, а значит снизили их влияние на общую продолжительность.


Этап 3. Оптимизация очередности автотестов в тестовом запуске

Мы не используем mock-объекты, а тестируем реальный бизнес-процесс, поэтому в запусках бывают тесты длительностью до 40 минут. С помощью Allure timeline мы заметили, что возникают ситуации, когда практически все тесты завершились, но остался один 30-минутный. Получается, что все ждут самого медленного, который еще одним из последних запустился. Встал вопрос, как продвинуть такой тест в самое начало.

Мы пишем автотесты на С# с помощью nunit framework. А он не предлагает удобного способа управлять очередностью запуска автотестов. Nunit предоставляет только order — атрибут, который работает только в рамках одного namespace, а сами namespace сортируются в алфавитном порядке. Поэтому самый простой способ подвинуть тест в самое начало — проставить букву А перед его названием в общем namespace.

Мы пошли дальше и сделали сортировку по строкам — названиям тестов. Тестам, которые длятся более 25 минут, мы присваиваем букву А вначале. Тестам от 20 до 25 минут — букву B, от 17 до 20 — букву С, от 14 до 17 — D и от 10 до 14 — букву E.

Чтобы определить, какие тесты пойдут без очереди, мы настроили билд в Jenkins. Он регулярно проверяет, начинаются ли с корректной буквы тесты длительностью от 15 минут. Если нет, то присылает уведомление команде автоматизации.

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


Этап 4. Увеличение параллелизации автотестов

В компании есть свои дата-центры. Под задачу нам выделили три новых мощных сервера. Так как серверы были дополнительными, мы смогли провели эксперименты с различными конфигурациями, чтобы найти оптимальную конфигурацию selenium grid. 

Стандартно мы запускали автотесты в 30 потоков. На новые сервера добавили пару сотен виртуальных машин (максимально до 10 браузеров на каждой), увеличили количество потоков до 200 и стали ждать результатов. Мы удивились, что тестовый запуск при 200 потоках продолжался гораздо дольше, чем при 30.

Разбирая ситуацию, заметили, что проблема оказалась в API методах: то, что при 30 потоках выполнялось за 20 секунд, при 200 потоках занимало 4-6 минут. Также мы узнали о ServicePointManager.DefaultConnectionLimit — максимальном количестве параллельных коннекшенов для .net продукта. Когда увеличили их до 200, тесты побежали быстрее, но все равно не так быстро, как ожидалось. Появилось много flaky test.

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

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

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

Меняя такие параметры, как количество виртуальных машин на один физический сервер, количество потоков, количество параллельных запусков, мы пришли к наиболее оптимальной конфигурации:  1 управляющий сервер, 15 виртуальных машин с selenium node, до 10 браузеров на каждом. 

Фактически сейчас мы имеем 7 Jenkins agents, на каждом из которых развернут свой собственных selenium hub c  2-мя executors.


Результаты

Мы решили поставленную задачу и ускорили автоматизированное тестирование. В результате мы смогли достичь следующих показателей:  

  • Прохождение одного автотеста сократилось с 2 часов до 1 часа;

  • Сократилось тестирование патча в среднем на 8 часов;

  • Увеличилась полнота автотестов;

  • Они стали стабильнее;

  • Код стал чище.

Источник: https://habr.com/ru/company/sdventures/blog/589845/


Интересные статьи

Интересные статьи

Здравствуйте! Сегодня хочу рассказать о нашем опыте тестирования скриншотами с использованием python, selenium, и Pillow.Зачем? У нас был довольно большой (~1000) набор т...
Предлагаю ознакомиться с ранее размещенными материалами по проекту Starlink (SL): Часть 1. Рождение проекта ‣ Часть 2. Сеть SL ‣ Часть 3. Наземный комплекс ‣ Часть 4. Абонентский те...
Доброго времени суток, коллеги. Я решил поделиться своим видением на параметризованные юнит-тесты, как делаем это мы, и как возможно не делаете(но захотите делать) вы. Хочется написать ...
Фото Chris Keats на Unsplash Многие компании, и мы в том числе, перешли от монолитов к микросервисам ради лучшей масштабируемости и ускорения циклов разработки. У нас всё еще есть монолитные п...
Если Вы используете в своих проектах инфоблоки 2.0 и таблицы InnoDB, то есть шанс в один прекрасный момент столкнуться с ошибкой MySQL «SQL Error (1118): Row size too large. The maximum row si...