Привет, Хабр! Меня зовут Артем Иванюта, в «Магните» я занимаюсь тестированием информационных систем закупок. В статье я расскажу, как наша команда запускала автотесты web-интерфейсов силами одного сотрудника, как мы вписали их в CI/CD-процесс и с чем столкнулись, решая задачу. Кстати, вы наверняка уже догадались, но все-таки скажу — да, я и есть тот самый «один сотрудник». Так что никакого кликбейта.
Одиннадцать друзей Иванюты
В нашей команде 11 человек, мы отвечаем за тестирование 15 информационных систем. Всего в «Магните» их больше 600. Мы занимаемся тестированием web-инструментов цепочки поставок розничной сети. Это, например:
система автоматизированных рабочих мест сотрудников;
SRM-система снабжения сырьем собственных производств и закупочной логистики;
электронный документооборот EDI;
информационная система графика поставок товара в магазины,
система управления ареалами AMS.
В масштабах «Магнита» это 25 000 пользователей — наших сотрудников, 4 500 — контрагентов и 16 000 торговых точек. Мы производим релизы ежедневно, а сам цикл в среднем составляет от 2 до 4 недель. По сути от нас зависит своевременная поставка товаров тысяч поставщиков на полки 16 тысяч магазинов (и много чего еще).
Восстание машин: срываем релизы
Мой путь в компании начался в 2014 году с отдела технического сопровождения торговых точек. Я занимался удаленной поддержкой и настройкой оборудования в гипермаркетах «Магнит». Это были кассы, системы эквайринга, серверы.
В 2018 году отдел тестирования запустил внутреннюю школу тестировщиков. За месяц я освоил базу и навыки, а затем перешел в команду тестирования web-интерфейсов. Пришел я как раз вовремя: поток входящих задач начал стремительно расти. Компания пошла по пути DevOps. Запускались новые системы, серьезно обновлялись основные.
К началу 2019 года объем тестирования увеличился вдвое. Так количество тикетов на каждого возросло в среднем с 30 до 70 в месяц, а горизонт планирования релизов сдвинулся на 2 месяца вперед.
При этом количество людей в команде оставалось прежним — все те же 11 друзей Иванюты:)
Когда графики релизов начали срываться на регулярной основе, а рутинные операции занимать больше 50% времени, мы поняли, что дальше так не потянем. Тогда и было принято решение часть сценариев покрыть автотестами.
Путь в питонисты
До 2019 года никто из нашей команды не занимался автотестами, 100% тестов обрабатывались вручную. Никто из нашей команды не умел кодить. И конечно мы не могли снижать темп основных задач. Поэтому всей командой уйти в обучение автоматизации тестирования тоже было нельзя. Решили, что в разведку пойдет кто-то один. Мне было интересно попробовать: я изучил опыт сообщества и остановился на python. Python считается универсальным языком, поскольку подходит под множество задач. К примеру, на Python написан Instagram, его используют в аналитике данных, запуске космических кораблей и... в автоматизации тестирования. Чтобы скорость работы команды не снижалась, приняли совместное решение — я иду обучаться, команда забирает 80% моих задач.
Какие цели мы поставили для внедрения автотестов:
-
Увеличение скорости тестирования.
Мы накопили довольно большую библиотеку регрессионных сценариев тестирования различных веб-интерфейсов. Релизы в команде проводятся каждый день, поэтому регресс востребован и выполняется тестировщиками регулярно вручную. Его выполнение занимало не меньше 8 часов, а автоматизированные тесты могли сократить время на обработку регресса до 95% и больше.
-
Повышение качества тестирования.
При частом прогоне рутинных проверок всегда есть риск пропустить ошибку. Машина же совершит её с меньшей вероятностью за счёт многократного прогона автотестов. Разработчик самостоятельно может запустить автотестирование после изменения кода без помощи тестировщика. То есть это время команды освобождается для других задач.
-
Рост экспертизы.
Разработанные автотесты нужно сопровождать и развивать. Мы решили сделать ставку на развитии своей команды и запустили "Школу автотестирования". Я на себе прочувствовал, как «заряжает» обучение на реальных задачах. В Магнит я прошел такой опыт дважды, получив и новую экспертизу, и развитие. Программу Школы выстроили по такому же принципу — много практики и реального опыта.
-
Поддержка DevOps-стратегии компании.
В 2018 году «Магнит» взял курс на развитие DevOps. Наши 15 систем не стали исключением. Их ждали обновление и переход на новые практики и инструменты. Автотесты — одна из обязательных стадий процесса CI/CD.
Вот так архитектурно он выстроен у нас:
Так мы договорились о целях и тактике. Следующая задача — понять, что именно покрывать автоматизацией.
Автоматизируем регресс
Мы обновляем информационные системы «Магнита» под условия бизнеса практически ежедневно:
Во-первых, мы следуем конкретному плану изменений и обновлений систем. Такой план формируем внутри ИТ ежемесячно для каждой системы.
Во-вторых, пользователи от бизнеса направляют запросы на новый функционал. В среднем на сотрудника приходится по 2–3 системы в день.
Получается, попытка автоматизировать все кейсы сразу была бы обречена на провал: сил могло хватить только на актуализацию тестовых сценариев.
После анализа всей базы тестовых случаев для автоматизации выбрали регрессионные сценарии.
Для нас регресс — один из самых трудоемких этапов тестирования, так как он направлен на проверку и выявление ошибок в статичном функционале. Такой функционал или не меняется, или меняется редко. Значит, каждый день мы выполняем именно типовые проверки.
В каждом регрессионном сценарии более 100 кейсов. Зачастую кейсы связаны между собой. Поэтому первое, что я сделал, — провел анализ тестовых случаев на предмет критичности и взаимосвязи функциональности.
В TMS системе TestRail создал 3 группы приоритета регрессионных сценариев для последующей автоматизации:
Критичный — например, кейсы направлены на проверку доступности всех вкладок, кнопок, фундаментальной логики приложения. Ошибки в этой группе тестов могут привести к полной блокировке работы инструмента;
Высокий — кейсы в большей степени направлены на проверку бизнес-логики и алгоритмов инструмента. Ошибки в этой группе тестов могут привести к некорректным данным, повлиять на работу самого приложения и связанных с ним систем;
Низкий — кейсы не влияют на бизнес-процессы, но должны выполняться при каждом регрессе. Например, поиск, фильтрация и другие.
Теперь расскажу про пошаговую настройку процесса и инструментов.
Запускаем змея на Pytest
Я использовал Python и его фреймворк Pytest. Оба инструмента широко используются в мире тестирования, по ним накоплена база знаний, есть много плагинов. Поэтому вместе с Selenium WebDriver такой набор удовлетворяет всем потребностям в тестировании web-интерфейсов.
Для работы с базой данных я использовал библиотеку SQLAlchemy. Это одна из самых популярных библиотек для работы с СУБД для Python.
При создании структуры проекта брал паттерн Page Object. Этот шаблон проектирования позволяет разделить код тестов и описание страниц. Так тесты приобретают читаемый вид, а методы работы со страницей мы можем переиспользовать. Результат: упрощаем поддержку кода и уменьшаем его количество.
Структура проекта выглядит так:
Папки:
Configuration – для настройки подключения к БД, учетные данные для авторизации в приложении;
Locators – хранит локаторы для поиска элементов на странице;
Pages – содержит методы для работы со страницами. Для каждой вкладки приложения используется своя страница с методами работы с ней;
Tests – хранит сами тесты;
Tools – хранит инструменты для подключения к БД. Это создание самого подключения, методы работы с БД, запросы к БД.
Файлы:
conftest.py — хранение фикстур. Фикстуры в контексте pytest — это вспомогательные функции для наших тестов, которые не являются частью тестового сценария;
pytest.ini – для регистрации меток маркировки тестов;
requirements.txt – файл зависимостей приложения для автоматической установки пакетов python с помощью утилиты pip;
testrail.cfg – конфигурационный файл с настройками для TestRail.
Помещаем в base_page методы или локаторы, которые используются в кейсах для разных страниц:
Как в итоге выглядит тест? Лаконично :)
@pytestrail.case('C128952')
@pytest.mark.order_gm
def test_btn_filter(self):
self.orders_gm.click_on_btn_filter()
self.orders_gm.should_be_open_filter_form()
self.orders_gm.click_on_whs()
self.orders_gm.input_name_gm_in_filter_form()
self.orders_gm.click_on_search_trigger()
self.orders_gm.click_on_x_tree_selected()
self.orders_gm.click_on_whs()
self.orders_gm.input_docdate_from()
self.orders_gm.input_docdate_to()
self.orders_gm.click_on_btn_filter_2()
self.orders_gm.should_be_row_in_grid_table_3()
В pages мы расписываем сами методы. Клик на кнопку:
def click_on_btn_filter(self):
btn = self.browser.find_element(*BasePageLocators.BUTTON_FILTER)
btn.click()
Локаторы отдельно:
BUTTON_FILTER = (By.XPATH, '//button[text() = "Фильтр"]')
Для поиска элементов на странице я использовал язык запросов XPath. Часть элементов страницы имеют уникальные name или id, которые указали разработчики фронта, поэтому к ним легко обращаться:
CONTR_ID = (By.XPATH, '//*[@id = "contr_id_view"]')
А другая часть не имеет таких уникальных идентификаторов, поэтому здесь помогают возможности языка XPath:
X_COMBO_SELECTED_LAST = (By.XPATH, '(//*[@class = "x-combo-list-item x-combo-selected"])[last()]')
Запуск и закрытие браузера вынесены в conftest.py.
Добавляем функцию, например так:
def pytest_addoption(parser):
parser.addoption('--browser_name', action='store', default="chrome",
help="Choose browser")
И фикстуру:
@pytest.fixture(scope="function")
def browser(request):
browser_name = request.config.getoption("browser_name")
if browser_name == "chrome":
capabilities = {
"acceptInsecureCerts": True,
"browserName": "chrome",
"version": "73.0",
"enableVNC": True,
"enableVideo": False,
"name": "Interface name"
}
browser = webdriver.Remote(command_executor="http://10.8.153.230:4444/wd/hub",
desired_capabilities=capabilities)
browser.maximize_window()
else:
raise pytest.UsageError("--browser_name should be chrome or firefox")
yield browser
browser.quit()
В самом тесте мы не расписываем авторизацию и переход на нужную вкладку, это все выносится в setup с помощью фикстуры:
@pytest.fixture(scope="function", autouse=True)
def setup(self, browser):
# Переход к странице авторизации
login_page = LoginPage(browser)
login_page.open()
login_page.authentication()
# Переход на вкладку
login_page.open_tab_orders_gm()
self.orders_gm = OrdersGMPage(browser, browser.current_url)
В параметрах фикстуры уже можно указать, когда его использовать. В данном случае наш setup запускается перед каждой функцией (тестом) с помощью параметра scope="function"
.
Ключ на старт: запускаемся через Gitlab CI
Для каждого проекта есть свой job в Gitlab CI, который запускается при необходимости. Последняя версия проекта тянется с репозитория.
Прогон самих тестов происходит на Selenoid. С помощью графической оболочки можно увидеть информацию о запущенных браузерах, их версиях, разрешении.
Развернув интерфейс тестирования можно увидеть, что происходит в режиме реального времени:
Selenoid создаёт для каждого теста чистый браузер, в котором выполняется автотест. После его завершения происходит удаление данных с помощью Python. Тесты не зависят друг от друга и могут запускаться в несколько потоков. Тестировать приложение можно одновременно с нескольких браузеров. Для запуска тестов в потоках используется pytest-xdist плагин. В команду запуска добавляется параметр -n <количество потоков>.
Фреймворк Pytest позволяет выбрать часть кейсов или пропускать те, которые сейчас не нужны. Например, для кейсов по одной вкладке приложения ставим маркировку @pytest.mark и запускаем тестирование нужной вкладки приложения. Фикстуры позволяют запустить тестирование в нужной версии браузера.
Получаем отчет в TestRail
Основным инструментом получения отчетности служит TestRail. Каждый автотест связан с кейсом в TestRail. Для связи использую глобальный идентификатор кейса через плагин pytest_testrail. Настройка выполняется в конфигурации testrail.cfg на основе документации. В начале кейса нужно добавить строчку вида @pytestrail.case('C128952')
.
Так выглядит результат выполнения автотестов, переданный в TestRail:
В названии передаю id джобы и название интерфейса, который тестирую. Вижу полную информацию по тестам, которые прошли успешно:
и детальную информацию по ошибкам, если они есть:
Как интегрировать автотесты в CI-процесс
Файлы с автотестами добавляю в репозиторий, где ведется разработка самого приложения. При пуше видим последнюю версию тестов. Далее добавляю stage: autotest в файл конфига сборки .gitlab-ci.yml. Команда для запуска может выглядеть например так:
- pytest -v ./test/ams_selenium_autotest/ --testrail --tr-testrun-name={$CI_PIPELINE_ID}_AMS_2.0_Automated_Run
Переменная CI_PIPELINE_ID
добавлена для идентификации сборки в TestRail.
После пуша в ветку запускается сборка по алгоритму. Описание храним в файле gitlab-ci.yml. Если стадия автотестов пройдёт успешно, то актуальная версия выкатит в продакшн.
Автотесты во спасение или Выход из штопора
План сработал, мы смогли запустить автоматизацию и разгрузить команду:
Среднее время выполнения регрессионного сценария сократилось на ~95% и стало занимать 20 минут;
Сократилось время выпуска релизов (Time To Market), а их количество в месяц выросло с 4 до 6;
Добились отказоустойчивости программного продукта при поставках, исключили риски в регрессе;
Из 1000 тестовых кейсов повысили количество покрытых автотестами до 400.
Этот год стал челленджем для всей команды и для меня лично.
Мы сделали выбор в пользу TestRail из-за его интеграции с основным инструментом тестирования Pytest. Плюсом стало наличие готовых фреймворков. Gitlab CI используется в разработке информационных систем коммерции и полностью соответствует нашему запросу. Поэтому предпочли тому же Jenkins.
По хорошей традиции запустили школу «Автоматизации тестирования» на практике, наш опыт может быть полезным другим командам тестирования в «Магните». Первые студенты-тестеры завершили обучение. Так практические навыки распространяются в компании.
Наша команда покрыла автотестами регресс на web-приложениях, сейчас мы готовы автоматизировать функциональное тестирование. Впереди более сложные задачи, и конечно нам будет полезен ваш опыт:
С какими проблемами перехода на автоматизацию тестирования столкнулись вы?
Как решали сложные кейсы автоматизации функционального тестирования?
Комментарии (20)
Octember
12.10.2021 22:37Спасибо за статью, выглядит круто! Если не секрет, подскажите, пожалуйста, как поднят selenoid? Насколько знаю, он не очень дружит с k8s
Temak01 Автор
13.10.2021 16:15Благодарю за оценку! Selenoid поднят на отдельной ВМке как раз таки по той причине, что в кубере его поднимать не советуют.
Faenor
13.10.2021 17:05В кубере можно поднять moon, тот же селеноид. За время использования с UI тестами на java и на python проблем не было. Так что можно и без ВМ)
Temak01 Автор
14.10.2021 15:25С moon хороший вариант, согласен. Но на данном этапе нас устраивает текущая архитектура. К тому же, moon - это платная версия селеноида)
Abstract35
12.10.2021 22:37А чем оправдано отдельное размещение локаторов на элементы и методов работы с ними?
Temak01 Автор
13.10.2021 16:18+1Локаторы решено было разместить отдельно, т.к. один и тот же локатор может применяться в разных методах, тем самым его можно переиспользовать и проще поддерживать в случае необходимости его изменения.
Sviatoslav2193
14.10.2021 11:34Один и тот же локатор может использоваться в разных методах
Традиционно для этого все локаторы и методы обычно объединяютсч внутри одного класса, к примеру класс LoginPage имеет атрибуты email_input и password_input, которые используются как в методе логина.
В то же время класс RegisterUserPage имеет метод регистрации нового пользователя, и может для переиспользования локаторов email_input и password_input отнаследоваться от LoginPage.
Пишу поскольку также несколько удивлён такому необычному разделению и не вижу его преимуществ, только лишние сущности в виде дополнительных модулей )
Tekill
14.10.2021 12:22+2Тут зависит от того, как у вас выглядит фронтенд. Если это админка со своей дизайн системой, то для одинаковых компонент селекторы проще вынести отдельно. Например, для того же хедера или элементов попапа.
А вообще в прошлом мой тимлид на подобный вопрос отвечал: "люблю, когда везде все упорядочено по папкам и классам" :)
Kirstan
25.10.2021 11:32+1Довольно структурированно и мотивирующе выглядит статья, благодарю ) Тоже хочу попробовать на каком-нибудь небольшом проекте питон попробовать, а то у нас всё на java, а у меня с ней как-то не сложилось ))
Temak01 Автор
28.10.2021 00:14Благодарю за оценку) Если есть возможность, то обязательно стоит попробовать!)
Angry_mish
03.11.2021 16:34В нашей команде 11 человек, мы отвечаем за тестирование 15 информационных систем
Вопрос не по автоматизации, конечно. Но вы уверены, что с таким подходом вы не перегружаете сотрудников, и можете обеспечить качественную экспертизу по каждой ИС?
Получается, что на 1 сотрудника у вас приходится больше, чем по одной системе, а учитывая специфику ритейлера - вряд ли системы простые. Вы уверены, что в погоне за автотестами (стандартная практика обучения по работе проводить в рабочее время) вы не теряете в качестве?
Temak01 Автор
03.11.2021 16:35Все информационные системы, которые тестируются нашей командой, разные по сложности и критичности. По самым критичным и трудоемким у нас выделено на 100% по одному эксперту, также мы стараемся поддерживать компетенции на уровне нескольких специалистов. Это нужно для выравнивания нагрузки и взаимозаменяемости при уходе сотрудника в отпуск или на больничный. На текущий момент такая модель нас полностью устраивает.
R-ch
Артём ,а можешь винбекап починить ?
Temak01 Автор
Могу посоветовать обратиться в службу поддержки компании или на портал самообслуживания, зарегистрировать заявку, её рассмотрят в порядке очереди и обязательно всё починят)
R-ch
я помню нам чинил его в 2015 году , нам понравилась как это было , робот стал работать просто очень шустро