Как собирать статистику по pytest-прогонам в Test IT

yesvmbv9hmgipggzj4smjjnbnjk.jpeg
Как известно, основной проблемой в тестировании является отчетность по прогонам. Некоторые компании собирают данные в отдельном хранилище. Вместо того, чтобы вручную организовывать хранение, было решено сохранять их в Test IT. Такие данные как: исход, время выполнения и количество автоматизированных кейсов позволяют разделить тесты на выборки и дать оценку покрытия автотестами.

Это практично, поскольку в Test IT всё сделали до нас и не придется заново изобретать велосипед. Объединив Test IT и pytest мы сможем выборочно запускать авто тесты. В итоге должна получиться мастер система, из которой будут запускаться тестовые выборки и собираться статистика.

Несмотря на то, что существует множество СУП (Allure TestOps, TestLink и т.п.), решили остановится на Test IT, поскольку это быстро растущий проект по доступной цене с поддержкой на русском языке, а лицензия на TestRail уже закончилась.


Техническое задание

В рамках задачи следует:


  • Настроить проект в Test IT
  • Настроить прохождение pytest-тестов в GitLab CI
  • Написать плагин для pytest который:
    • создает автоматизированный кейс в Test IT
    • связывает автоматизированный и ручной кейс
    • запускает только те автоматизированные кейсы, которые были выбраны в прогоне
    • отмечает результаты в режиме реального времени


Замечания


Реализация


1. Создание и связывание автоматизированных кейсов в Test IT

В Test IT существует два вида кейсов: ручной и автоматизированный

islnmb9xdamxwrnvmdbygkrfobc.png

Для того чтобы в прогоне появилась кнопка Launch Autotests автоматизированный кейс должны быть создан и привязан к ручному

pijxmntw2_pfnmgzb9a2q4sorh0.png

В таком случае напротив ручного кейса появится символ ракеты

cztmw7wlasvz7tr2aoia3rvqheu.png

Создание и связывание автоматизированного кейса происходит через endpoint AutoTests, в который следует передать два параметра:


  • testit_case_title — название автоматизированного кейса
  • testit_case_id — идентификатор ручного кейса для привязки
@pytest.mark.testit_case_id(29035)
@putest.mark.testit_case_title("Проверка первой успешной загрузки файла")
def test_download_first(test_it_configuration):
    """
        Автотест прилинкованный к ручному тесту
    """
    from pprint import pprint
    pprint(test_it_configuration)
    sleep(randint(a=0, b=8))

Для запуска GitLab CI агента через Test IT следует создать WebHook со следующими параметрами:

yjidsnebeq_ifykqyqwjtowtlzc.png

Параметры для GitLab CI:


  • URL — ссылка на trigger от GIT-проекта
  • ref — ветка, в которой запускаются тесты
  • token — токин от GitLab CI триггера

Параметры для запуска PYTEST:


  • TEST_IT — флаг сообщающий, что тесты запущены из Test IT
  • TESTIT_URL — ссылка на Test IT
  • API_KEY — ключ для Test IT Swagger API
  • PROJECT_ID — идентификатор проекта в котором вы работаете
  • TEST_IT_MODE — режим работы с Test IT

TEST_IT_MODE принимает следующие значения:


  • DELETE_AUTOTESTS
  • CREAT_AUTOTESTS
  • LINK_AUTOTESTS
  • RUN

Эти параметры будут переданы в GitLab CI и сохранены в переменных окружения агента

-sclmjytrtfpww8eh_awtmp3epi.png

Тесты получают значения из переменных окружения агента при помощи модуля getenv

from os import getenv

class TestItParams:
    """
        Параметры из Test IT
    """
    is_test_it = eval(getenv('TEST_IT', 'False'))
    mode = getenv('TEST_IT_MODE', 'REGULAR')
    url = getenv('TESTIT_URL', 'http://tmslt-1.video.rt.ru/')
    key = getenv('API_KEY', None)
    project_id = getenv('PROJECT_ID', None)
    run_id = getenv('TEST_RUN_ID', None)
    configuration_id = getenv('CONFIG_ID', None)

Класс TestItParams следует сохранить в объекте session

def pytest_sessionstart(session):
    """
        SetUP тестовый сессии: собрать env-параметры из TestIT
    """

    session.test_it = TestItParams

Для создания автоматизированного кейса во время запуска pytest, нам нужно достать два параметра testit_case_title и testit_case_id.

Эти параметры можно получить из pytest_collection_modifyitems, поскольку он вызывается во время составления списка на запуск.

Для этого напишем plugin в файле conftest.py

def pytest_collection_modifyitems(session, items):
    """
        Перехват состояния перед публикацией списка кейсов
    """

    if session.test_it.is_test_it is True:
        print('Тесты запущены для TEST_IT')

        if 'DELETE_AUTOTESTS' in session.test_it.mode:
            delete_all_auto_test_from_project(pytest_session=session)
        if 'CREAT_AUTOTESTS' in session.test_it.mode:
            create_autotest_in_project(pytest_session=session, pytest_items=items)
        if 'LINK_AUTOTESTS' in session.test_it.mode:
            link_autotests_to_testcases(pytest_session=session, pytest_items=items)
        if 'RUN' in session.test_it.mode:
            select_autotest_from_testrun_only(pytest_session=session)
    else:
        print('\nПроходит штатный запуск')

Функции create_autotest_in_project и link_autotests_to_testcases это обычные API запросы через endpoint AutoTests.

Параметр test_it_mode используется для разделения задач: создание, привязка, удаление или запуск.


2. Выборочный запуск pytest-тестов

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

Поскольку pytest ничего не знает о тестовом плане, то он должен запрашивать его самостоятельно.

Для этого по значению параметра TEST_RUN_ID через endpoint TestRunes мы получаем список тестов в текущем прогоне.

q1kks3ifbvpps8tu22vxyjt2s2s.png

Далее выбираем только те тесты, которые есть в прогоне и удаляем остальные.

def pytest_collection_modifyitems(session, items):
    """
        Перехват состояния перед публикацией списка кейсов: создание, удаление, линковка, выборка
    """

    if session.test_it.is_test_it is True:
        print('Тесты запущены для TEST_IT')

        if 'DELETE_AUTOTESTS' in session.test_it.mode:
            delete_all_auto_test_from_project(pytest_session=session)
        if 'CREAT_AUTOTESTS' in session.test_it.mode:
            create_autotest_in_project(pytest_session=session, pytest_items=items)
        if 'LINK_AUTOTESTS' in session.test_it.mode:
            link_autotests_to_testcases(pytest_session=session, pytest_items=items)
        if 'RUN' in session.test_it.mode:
            select_autotest_from_testrun_only(pytest_session=session)
    else:
        print('\nПроходит штатный запуск')

Фильтрация происходит в объекте session.items в момент составления списка на запуск.

def select_autotest_from_testrun_only(pytest_session):
    """
        Выбрать для запуска только те кейсы, которые содержатся в тестовом прогоне
    """

    run_api = TestRunes(pytest_session.test_it.url, pytest_session.test_it.key)
    current_test_run = run_api.get_testrun_by_id(pytest_session.test_it.run_id)

    external_ids = []
    for case in current_test_run['testResults']:
        if case['startedOn'] is None:
            external_ids.append((case['autoTest']['externalId'],
                                 case['configuration']))

    edited_session_items = []
    for case in pytest_session.items:
        for external_id in external_ids:
            if case.name in external_id[0]:
                case.test_it_configuration = external_id[1]
                edited_session_items.append(case)
    pytest_session.items = edited_session_items


3. Возврат результата прохождения pytest-тестов

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

Для возвращения статуса в реальном времени нам следует воспользоваться хуком pytest_runtest_makereport с декоратором @pytest.hookimpl (hookwrapper=True).

Он позволяет перехватывать выполнение ОДНОГО теста ДО и ПОСЛЕ прохождения.

@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item):
    """
        Перехватывает состояние каждого теста во время: setup и done
    """

    outcome = yield

    if 'RUN' in item.session.test_it.mode:
        provide_test_results_into_run(pytest_session=item.session,
                                      pytest_outcome=outcome,
                                      pytest_item=item)

Этот хук возвращает объект outcome, в котором содержится информация о прогоне текущего теста.

Передача результатов прогона в Test IT происходит через через endpoint TestRunes.

def provide_test_results_into_run(pytest_session, pytest_outcome, pytest_item):
    """
        Предоставить результат выполнения теста в TestIT
    """
    test_run_api = TestRunes(pytest_session.test_it.url, pytest_session.test_it.key)
    res = pytest_outcome.get_result()
    if res.when == "call":
        autotest_id = pytest_item.name
        outcome = res.outcome
        duration = res.duration
        message = ''
        traceback = ''

        if res.outcome == 'failed':
            message = res.longrepr.reprcrash.message
            traceback = res.longreprtext

        test_run_api.set_auto_test_results_for_test_run(run_id=pytest_session.test_it.run_id,
                                                        autoTestExternalId=autotest_id,
                                                        outcome=humanize(outcome),
                                                        message=message,
                                                        traces=traceback,
                                                        duration=duration,
                                                        configurationId=pytest_item.test_it_configuration['id'])


Вывод

Объединив Test IT и pytest мы собрали мастер систему которая умеет:


  • запускать автотесты в GitLab CI
  • получать результат в режиме реального времени
  • собирать статистику по запускам
  • запускать выборки тестов

Test IT имеет красивый интерфейс, предоставляющий кольцевые-диаграммы, различные таблицы и графики. Можно узнать, в каком прогоне упал конкретный тест и сколько времени он выполнялся.


Демонстрация

laudqtwl3tipntvhu12metoztvi.gif


Ссылки


© Habrahabr.ru