Настройка GitHub Actions для автоматизированного тестирования средствами Python в конвейере CI/CD

от автора

В преддверии старта курса «Python QA Engineer» традиционно публикуем перевод полезного материала.

Также приглашаем присоединиться к открытому вебинару на тему «Автоматизация тестирования API».


В этой статье описываются операции по тестированию клиентской части приложения с помощью TestProject и pytest, а также способы выполнения тестов через GitHub Actions. Если у вас общедоступный репозиторий GitHub, все это будет совершенно бесплатно. Эта возможность хорошо подходит для изучения TestProject и выполнения интеграционного тестирования в ваших проектах. Если вы хотите выполнять эти операции из закрытого репозитория, то GitHub предлагает очень большое количество бесплатных минут, см. https://github.com/features/actions#pricing-details.

Карточки репозиториев GitHub

Сперва я решил изучить возможности, доступные в репозиториях GitHub, поскольку они показались мне довольно простыми для понимания, а к тому же я давно не пользовался Selenium.

Я закрепил свои любимые репозитории в верхней части своего сайта waylonwalker.com. Информация для заполнения этих карточек динамически подтягивается на сторону клиента через API-интерфейс GitHub. Это означает, что по мере загрузки страниц JavaScript выполняет скрипты для получения информации от API-интерфейса GitHub, а затем преобразует эти данные в DOM и визуализирует их на странице. Вот как выглядят карточки репозиториев GitHub:

Получение ключей

Прежде всего, вам понадобятся TPDEVTOKEN и TPAPIKEY. Эти ключи позволят TestProject получить доступ к вашей учетной записи, чтобы автоматически размещать результаты на вашей панели отчетности.

В своем репозитории GitHub перейдите в раздел settings >Secrets (или добавьте settings/secrets к URL-адресу своего репозитория) и добавьте секретные токены. GitHub получит безопасный доступ к токенам, при этом они не будут доступны широкой публике (включая участников проекта со статусом contributor), не будут отображаться в файлах журналов и т. д.

Настройка среды разработки

Чтобы ускорить работу, я настроил среду разработки в Digital Ocean. Делать это необязательно: все может работать и с вашего локального компьютера или полностью из GitHub Actions. Просто я решил, что, настроив Droplet с Ubuntu в Digital Ocean, я получу условия, близкие к продакшн-среде, а значит, смогу быстрее разработать свои тесты. Это также позволило мне немного ускорить работу всех моих тестов по сравнению с их выполнением через GitHub. При этом процесс был практически таким же, как при использовании GitHub. Таким образом, я смог детально изучить принципы настройки TestProject без необходимости выполнять полную установку при каждом запуске GitHub Actions.

Я не буду здесь подробно описывать настройку машины для разработки. Мои заметки по ее настройке можно прочитать здесь: https://waylonwalker.com/notes/new-machine-tpio.

Pytest

Все тесты, выполняющиеся с помощью Pytest, приведены на github.

Я решил воспользоваться Pytest. Мне понравилась идея применять фикстуры, автоматически выполнять мои тестовые функции и использовать кое-какие возможности Pytest для формирования отчетов по ходу разработки (при этом платформа TestProject будет работать и без фреймворка для тестирования вроде Pytest).

ПРИМЕЧАНИЕ. Следуя стандартным рекомендациям по работе с Pytest, я назвал каталог с тестами именем tests. В целом все работает, но платформа TestProject.io использует этот каталог в качестве имени проекта по умолчанию. Если бы можно было вернуться назад, я либо переименовал бы каталог, задав имя, которое я хочу видеть на TestProject.io, либо задал бы имя проекта в конфигурации.

conftest.py

Вы можете посмотреть файл conftest.py на GitHub.

В файле conftest.py обычно размещаются фикстуры, которые используются несколькими модулями. Pytest автоматически импортирует все модули conftest.py из каталога, в котором вы работаете. Это отличное место для размещения фикстур с драйверами TestProject. Обратите внимание: когда вы используете фикстуру в качестве аргумента в другой функции, фикстура выполнит настройку, передаст все из оператора yield в тестовую функцию, выполнит тестовую функцию, а затем освободит ресурсы.

conftest.py хранит фикстуры для всех модулей, находящихся в каталоге.

# tests/conftest.py import time import pytest from src.TestProject.sdk.drivers import web driver @pytest.fixture def driver():     "creates a webdriver and loads the homepage"     driver = webdriver.Chrome()     driver.get("https://waylonwalker.com/")     yield driver     driver.quit()

Приведенный выше пример является немного упрощенным. В реальной версии я столкнулся с некоторыми несоответствиями и обнаружил, что процент прохождения некоторых тестов был выше при добавлении оператора time.sleep. В полном проекте я остановился на фикстурах driver и slowdriver. Таким образом, у меня еще есть драйвер, который ждет выполнения JavaScript немного дольше.

testrepos.py

Полная версия testrepos.py есть на GitHub.

Изначально я настроил три разных теста для карточек репозиториев. Я сформировал список репозиториев, которые должны были отображаться в карточках. Эти тесты довольно легко выполнить с помощью платформы TestProject.io, поскольку она использует Selenium и безголовый браузер для выполнения JavaScript. Область REPOS создана здесь как глобальный список. Ее можно легко переделать в файл конфигурации, если возникнет такая необходимость.

Для тех, кто не знает, скажу, что безголовый (headless) браузер работает как обычный браузер, только без графического интерфейса пользователя. JavaScript полностью загружается и парсится, а все взаимодействие с DOM осуществляется программно.

Читайте строки документации к каждой функции. В них описано то, что происходит на каждом шаге.

""" Test that GitHub repo data dynamically loads the client-side. """ REPOS = [     "find-kedro",     "kedro-static-viz",     "kedro-action",     "steel-toes", ] def test_repos_loaded(slow_driver):     """     Test that each repo-name exists as a title in one of the repo cards.     On waylonwalker.com repo cards have a title with a class of "repo-name"     """     repos = slow_driver.find_elements_by_class_name("repo-name")     # get innertext from elements     header_text = [         header.text for header in repos     ]     for repo in REPOS:         assert repo in header_text def test_repo_description_loaded(slow_driver):     """     Test that each repo has a description longer than 10 characters     On waylonwalker.com repo cards have a descriptiion with a class of "repo-description"     """     repo_elements = slow_driver.find_elements_by_class_name("repo")     for el in repo_elements:         desc = el.find_element_by_class_name("repo-description")         assert len(desc.text) > 10 def test_repo_stars_loaded(slow_driver):     """     Ensure that stars are correctly parsed from the API and loaded client-side     On waylonwalker.com repo cards have a stars element with a class of "repo-stars" and     is displayed as "n stars"     """     repo_elements = slow_driver.find_elements_by_class_name("repo")     for el in repo_elements:         stars = el.find_element_by_class_name("repo-stars")         num_stars, label = stars.text.split()         assert int(num_stars) > 0         assert label == 'stars'

Форум

Я немного запутался с настройкой TestProject.io в Actions. На форуме TestProject я быстро нашел ответ со ссылкой именно на тот пример, который был мне нужен. Пример был написан на Java, но в нем были нужные мне шаги настройки docker-compose.

GitHub Actions

[test-waylonwalker-com.yml]

Итак, репозиторий GitHub настроен, а мои тесты успешно работают в Pytest. Теперь давайте сделаем так, чтобы они автоматически выполнялись в GitHub Actions.

Сервис Actions — это решение GitHub для CI/CD. Он позволяет выполнять код на виртуальной машине, управляемой GitHub, которая может получать дополнительную информацию из вашего репозитория, например секретные токены, которые мы настроили в самом начале. Что, как и когда выполняется — все это настраивается в файле yaml.

# .github/workflows/test-waylonwalker-com.yml name: Test WaylonWalker.com # Controls when the action will run. Triggers the workflow on push or pull request # events but only for the master branch on:   push:     branches: [ main ]   pull_request:     branches: [ main ]   schedule:     - cron: '*/10 * * * *'

Как видно в приведенном выше куске кода, я задал выполнение действия при отправке изменений в ветку main (команда push) или при создании pull-запроса, затрагивающего эту ветку. Я также задал достаточно агрессивный график выполнения тестов — каждые 10 минут. Это было необходимо, чтобы убедиться в эффективности тестов и получить больше данных для анализа в отчетах. Позднее я, скорее всего, увеличу этот интервал.

jobs:   test:     runs-on: ubuntu-latest     steps:     - uses: actions/checkout@main     - uses: actions/setup-python@v2       with:         python-version: '3.8'     - run: pip install -r requirements.txt     - name: Run TestProject Agent       env:         TP_API_KEY: ${{ secrets.TP_API_KEY }} # < Let Secrets handle your keys       run: |         envsubst < .github/ci/docker-compose.yml > docker-compose.yml         cat docker-compose.yml         docker-compose -f docker-compose.yml up -d     - name: Wait for Agent to Register       run: bash .github/ci/wait_for_agent.sh     - run: pytest       env:         TP_DEV_TOKEN: ${{ secrets.TP_DEV_TOKEN }} # < Let Secrets handle your tokens         TP_AGENT_URL: http://localhost:8585

В коде задания test видно, что я решил выполнять его на ubuntu-latest. Первые три шага — это достаточно шаблонные действия: переключение на ветку репозитория, установка Python 3.8 и установка зависимостей из файла requirements.txt через pip. Затем ключ TPAPIKEY подставляется в docker-compose.yml с помощью envsubst, далее запускается docker-compose и ожидает готовности агента. Я также предоставил pytest наш токен TPDEVTOKEN и запустил pytest.

docker-compose.yml

Следующий файл docker-compose.yml был предоставлен Виталием Буховским (одним из сооснователей и директором по безопасности TestProject) в репозитории testproject-io/java-sdk. Здесь настраивается шаблон с ключом TPAPIKEY в качестве переменной для envsubst, безголовыми браузерами Chrome и Firefox и агентом TestProject.io.

version: "3.1" services:   testproject-agent:     image: testproject/agent:latest     container_name: testproject-agent     depends_on:       - chrome       - firefox     environment:       TP_API_KEY: "${TP_API_KEY}"       TP_AGENT_TEMP: "true"       TP_SDK_PORT: "8686"       CHROME: "chrome:4444"       CHROME_EXT: "localhost:5555"       FIREFOX: "firefox:4444"       FIREFOX_EXT: "localhost:6666"     ports:     - "8585:8585"     - "8686:8686"   chrome:     image: selenium/standalone-chrome     volumes:       - /dev/shm:/dev/shm     ports:     - "5555:4444"   firefox:     image: selenium/standalone-firefox     volumes:       - /dev/shm:/dev/shm     ports:     - "6666:4444"

Ожидание регистрации агента

waitforagent.sh

На мой взгляд, самая интересная часть приведенного выше потока операций — это то, как мы ожидаем регистрации агента. Скрипт оболочки довольно лаконичный. Он обнаруживает превышение допустимого количества попыток запуска (max_attempts) или наличие запущенного агента путем проверки статуса через REST API (адрес: /api/status). Это позволяет нам избежать потери времени, вызванной заданием слишком большого времени ожидания, или слишком раннего начала процесса, при котором pytest запускается, когда агент еще не работает.

trap 'kill $(jobs -p)' EXIT attempt_counter=0 max_attempts=100 mkdir -p build/reports/agent docker-compose -f docker-compose.yml logs -f | tee build/reports/agent/log.txt& until curl -s http://localhost:8585/api/status | jq '.registered' | grep true; do     if [ ${attempt_counter} -eq ${max_attempts} ]; then     echo "Agent failed to register. Terminating..."     exit 1     fi     attempt_counter=$(($attempt_counter+1))     echo     sleep 1 done

Панель отчетности TestProject

После запуска тестов они отображаются на панели TestProject. На ранней стадии разработки тестов возникло несколько сбоев, но после устранения неполадок тесты прогоняются стабильно.

Панель: прохождение одного теста

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

Подробнее о панелях отчетности TestProject можно прочитать по следующим ссылкам:

Это пособие создал Уэйлон Уокер (Waylon Walker) на основе своей статьи «Интеграционное тестирование с помощью Python, TestProject.io и GitHub Actions».

Удачного тестирования!


Узнать подробнее о курсе «Python QA Engineer».


Записаться на открытый вебинар «Автоматизация тестирования API».

ссылка на оригинал статьи https://habr.com/ru/company/otus/blog/530630/