Привет, Хабр! Меня зовут Артем Иванюта, в «Магните» я занимаюсь тестированием информационных систем закупок. В статье я расскажу, как наша команда запускала автотесты web-интерфейсов силами одного сотрудника, как мы вписали их в CI/CD-процесс и с чем столкнулись, решая задачу. Кстати, вы наверняка уже догадались, но все-таки скажу — да, я и есть тот самый «один сотрудник». Так что никакого кликбейта.

Одиннадцать друзей Иванюты

В нашей команде 11 человек, мы отвечаем за тестирование 15 информационных систем. Всего в «Магните» их больше 600. Мы занимаемся тестированием web-инструментов цепочки поставок розничной сети. Это, например:

  1. система автоматизированных рабочих мест сотрудников;

  2. SRM-система снабжения сырьем собственных производств и закупочной логистики;

  3. электронный документооборот EDI;

  4. информационная система графика поставок товара в магазины,

  5. система управления ареалами 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% моих задач. 

Какие цели мы поставили для внедрения автотестов:

  1. Увеличение скорости тестирования.

    Мы накопили довольно большую библиотеку регрессионных сценариев тестирования различных веб-интерфейсов. Релизы в команде проводятся каждый день, поэтому регресс востребован и выполняется тестировщиками регулярно вручную. Его выполнение занимало не меньше 8 часов, а автоматизированные тесты могли сократить время на обработку регресса до 95% и больше. 

  2. Повышение качества тестирования.

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

  3. Рост экспертизы.

    Разработанные автотесты нужно сопровождать и развивать. Мы решили сделать ставку на развитии своей команды и запустили "Школу автотестирования".  Я на себе прочувствовал, как «заряжает» обучение на реальных задачах. В Магнит я прошел такой опыт дважды, получив и новую экспертизу, и развитие. Программу Школы выстроили по такому же принципу — много практики и реального опыта. 

  4. Поддержка DevOps-стратегии компании.

    В 2018 году «Магнит» взял курс на развитие DevOps. Наши 15 систем не стали исключением. Их ждали обновление и переход на новые практики и инструменты. Автотесты — одна из обязательных стадий процесса CI/CD. 

Вот так архитектурно он выстроен у нас:

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

Автоматизируем регресс

Мы обновляем информационные системы «Магнита» под условия бизнеса практически ежедневно:

Во-первых, мы следуем конкретному плану изменений и обновлений систем. Такой план формируем внутри ИТ ежемесячно для каждой системы. 

Во-вторых, пользователи от бизнеса направляют запросы на новый функционал. В среднем на сотрудника приходится по 2–3 системы в день. 

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

После анализа всей базы тестовых случаев для автоматизации выбрали регрессионные сценарии. 

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

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

В TMS системе TestRail создал 3 группы приоритета регрессионных сценариев для последующей автоматизации:

  1. Критичный — например, кейсы направлены на проверку доступности всех вкладок, кнопок, фундаментальной логики приложения. Ошибки в этой группе тестов могут привести к полной блокировке работы инструмента;

  2. Высокий — кейсы в большей степени направлены на проверку бизнес-логики и алгоритмов инструмента. Ошибки в этой группе тестов могут привести к некорректным данным, повлиять на работу самого приложения и связанных с ним систем;

  3. Низкий — кейсы не влияют на бизнес-процессы, но должны выполняться при каждом регрессе. Например, поиск, фильтрация и другие.

Теперь расскажу про пошаговую настройку процесса и инструментов.

Запускаем змея на Pytest

Я использовал Python и его фреймворк Pytest. Оба инструмента широко используются в мире тестирования, по ним накоплена база знаний, есть много плагинов. Поэтому вместе с Selenium WebDriver такой набор удовлетворяет всем потребностям в тестировании web-интерфейсов.

Для работы с базой данных я использовал библиотеку SQLAlchemy. Это одна из самых популярных библиотек для работы с СУБД для Python. 

При создании структуры проекта брал паттерн Page Object. Этот шаблон проектирования позволяет разделить код тестов и описание страниц. Так тесты приобретают читаемый вид, а методы работы со страницей мы можем переиспользовать. Результат: упрощаем поддержку кода и уменьшаем его количество.

Структура проекта выглядит так:

Папки:

  1. Configuration – для настройки подключения к БД, учетные данные для авторизации в приложении;

  2. Locators – хранит локаторы для поиска элементов на странице;

  3. Pages – содержит методы для работы со страницами. Для каждой вкладки приложения используется своя страница с методами работы с ней;

  4. Tests – хранит сами тесты;

  5. Tools – хранит инструменты для подключения к БД. Это создание самого подключения, методы работы с БД, запросы к БД.

Файлы:

  1. conftest.py — хранение фикстур. Фикстуры в контексте pytest — это вспомогательные функции для наших тестов, которые не являются частью тестового сценария;

  2. pytest.ini – для регистрации меток маркировки тестов;

  3. requirements.txt – файл зависимостей приложения для автоматической установки пакетов python с помощью утилиты pip;

  4. 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. Если стадия автотестов пройдёт успешно, то актуальная версия выкатит в продакшн.

Автотесты во спасение или Выход из штопора

План сработал, мы смогли запустить автоматизацию и разгрузить команду: 

  1. Среднее время выполнения регрессионного сценария сократилось на ~95% и стало занимать 20 минут;

  2. Сократилось время выпуска релизов (Time To Market), а их количество в месяц выросло с 4 до 6;

  3. Добились отказоустойчивости программного продукта при поставках, исключили риски в регрессе;

  4. Из 1000 тестовых кейсов повысили количество покрытых автотестами до 400.

Этот год стал челленджем для всей команды и для меня лично. 

Мы сделали выбор в пользу TestRail из-за его интеграции с основным инструментом тестирования Pytest. Плюсом стало наличие готовых фреймворков. Gitlab CI используется в разработке информационных систем коммерции и полностью соответствует нашему запросу. Поэтому предпочли тому же Jenkins.

По хорошей традиции запустили школу «Автоматизации тестирования» на практике, наш опыт может быть полезным другим командам тестирования в «Магните». Первые студенты-тестеры завершили обучение. Так практические навыки распространяются в компании. 

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

С какими проблемами перехода на автоматизацию тестирования столкнулись вы? 

Как решали сложные кейсы автоматизации функционального тестирования?

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


  1. R-ch
    12.10.2021 15:16

    Артём ,а можешь винбекап починить ?


    1. Temak01 Автор
      12.10.2021 17:50
      +2

      Могу посоветовать обратиться в службу поддержки компании или на портал самообслуживания, зарегистрировать заявку, её рассмотрят в порядке очереди и обязательно всё починят)


      1. R-ch
        12.10.2021 17:54
        +1

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


  1. Woodli
    12.10.2021 17:52
    +1

    Неплохая статья


  1. Octember
    12.10.2021 22:37

    Спасибо за статью, выглядит круто! Если не секрет, подскажите, пожалуйста, как поднят selenoid? Насколько знаю, он не очень дружит с k8s


    1. Temak01 Автор
      13.10.2021 16:15

      Благодарю за оценку! Selenoid поднят на отдельной ВМке как раз таки по той причине, что в кубере его поднимать не советуют.


    1. Faenor
      13.10.2021 17:05

      В кубере можно поднять moon, тот же селеноид. За время использования с UI тестами на java и на python проблем не было. Так что можно и без ВМ)


      1. Temak01 Автор
        14.10.2021 15:25

        С moon хороший вариант, согласен. Но на данном этапе нас устраивает текущая архитектура. К тому же, moon - это платная версия селеноида)


  1. Abstract35
    12.10.2021 22:37

    А чем оправдано отдельное размещение локаторов на элементы и методов работы с ними?


    1. Faenor
      13.10.2021 16:02

      Так на курсе по Python автоматизации на Stepik сделано)


    1. Temak01 Автор
      13.10.2021 16:18
      +1

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


      1. Sviatoslav2193
        14.10.2021 11:34

        Один и тот же локатор может использоваться в разных методах

        Традиционно для этого все локаторы и методы обычно объединяютсч внутри одного класса, к примеру класс LoginPage имеет атрибуты email_input и password_input, которые используются как в методе логина.

        В то же время класс RegisterUserPage имеет метод регистрации нового пользователя, и может для переиспользования локаторов email_input и password_input отнаследоваться от LoginPage.

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


        1. Tekill
          14.10.2021 12:22
          +2

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

          А вообще в прошлом мой тимлид на подобный вопрос отвечал: "люблю, когда везде все упорядочено по папкам и классам" :)


  1. mithron
    13.10.2021 09:24

    Интересные у вас там страницы, судя по скриншоту.


    1. Temak01 Автор
      13.10.2021 16:19

      Спасибо.


    1. R-ch
      13.10.2021 21:52

      ты про bdsm ?


  1. Kirstan
    25.10.2021 11:32
    +1

    Довольно структурированно и мотивирующе выглядит статья, благодарю ) Тоже хочу попробовать на каком-нибудь небольшом проекте питон попробовать, а то у нас всё на java, а у меня с ней как-то не сложилось ))


    1. Temak01 Автор
      28.10.2021 00:14

      Благодарю за оценку) Если есть возможность, то обязательно стоит попробовать!)


  1. Angry_mish
    03.11.2021 16:34

    В нашей команде 11 человек, мы отвечаем за тестирование 15 информационных систем

    Вопрос не по автоматизации, конечно. Но вы уверены, что с таким подходом вы не перегружаете сотрудников, и можете обеспечить качественную экспертизу по каждой ИС?

    Получается, что на 1 сотрудника у вас приходится больше, чем по одной системе, а учитывая специфику ритейлера - вряд ли системы простые. Вы уверены, что в погоне за автотестами (стандартная практика обучения по работе проводить в рабочее время) вы не теряете в качестве?


    1. Temak01 Автор
      03.11.2021 16:35

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