PyTest Fixture

Содержание
Введение
Общая переменная
Изменить общую переменную
function
Порядок тестов
Перезапуск тестов
Suite setup и teardown
Похожие статьи

Введение

В этой статье вы можете познакомиться с фикстурами PyTest.

Официальная документация

Общая переменная

С помощью fixture можно обобществить какую-то перменную между тестами в модуле.

Для этого нужно указать тип фикстуры module

import random def generate_word(): route_name "" symbols ["A", "E", "H", "K", "L", "M", "N", "T", "4", "7", "8"] for _ in range(5): route_name += random.choice(symbols) return route_name

import pytest import json from utils import generate_word @pytest.fixture(scope="module") def shared_word(): return generate_word() def test_save_word(shared_word): data = {"value": shared_word} with open("output.json", "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=2) def test_word_is_saved(shared_word): with open("output.json", "r", encoding="utf-8") as f: data = json.load(f) assert data["value"] == shared_word

python -m pytest -vv test_fixt.py

============================= test session starts ============================= collecting ... collected 2 items fixtures/test_fixt.py::test_save_word PASSED [ 50%] fixtures/test_fixt.py::test_word_is_saved PASSED [100%] ============================== 2 passed in 0.02s ============================== Process finished with exit code 0

Изменить общую переменную

Часто бывает нужно не просто прочитать одно и то же значение в разных тестах, но и изменить его.

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

Фикстура должна будет возвращать какой-то изменямый (mutable) объект . Обычно это словарь.

import pytest @pytest.fixture(scope="module") def shared(): return {} def test_one(shared): shared["value"] = 1 assert isinstance(shared["value"], int) def test_two(shared): assert 2 == shared["value"] + 1 def test_three(shared): print("\n", shared) assert isinstance(shared, dict)

============================= test session starts ============================= collecting ... collected 3 items fixtures/shared.py::test_one PASSED [ 33%] fixtures/shared.py::test_two PASSED [ 66%] fixtures/shared.py::test_three PASSED [100%] {'value': 1} ============================== 3 passed in 0.01s ==============================

Про функцию isinstance() вы можете прочитать здесь .

Тот же самый результат можно получить и с помощью списка .

Заменим возвращаемый фикстурой объект на пустой список. Для разнообразия изменим список и во втором тесте тоже.

import pytest @pytest.fixture(scope="module") def shared(): return [] def test_one(shared): shared.append(1) assert isinstance(shared[0], int) def test_two(shared): shared[0] = shared[0] + 1 shared.append(8) assert 2, 8 == (shared[0], shared[1]) def test_three(shared): print("\n", shared) assert isinstance(shared, list)

============================= test session starts ============================= collecting ... collected 3 items fixtures/shared.py::test_one PASSED [ 33%] fixtures/shared.py::test_two PASSED [ 66%] fixtures/shared.py::test_three PASSED [100%] [2, 8] ============================== 3 passed in 0.01s ==============================

function

Чтобы фикстура использовалась каждым тестом, нужно задать уровень function

В следующем примере мы будем выводить на экран текст перед каждый тестом. Для этого нужно использовать флаг -s.

import pytest @pytest.fixture(scope="function") def announce_new_test(): print("\n\nINFO: TestSetup.ru ---- new test case ----\n") def test_one(announce_new_test): assert 1 == 1 def test_two(announce_new_test): assert 2 == 2

python -m pytest -sv test_fixt.py

====================== test session starts ====================== platform win32 -- Python 3.12.10, pytest-9.0.1, pluggy-1.6.0 -- C:\A\pytest\venv\Scripts\python.exe cachedir: .pytest_cache rootdir: C:\A\pytest plugins: order-1.3.0, rerunfailures-16.1 collected 2 items fixtures/f.py::test_one INFO: TestSetup.ru ---- new test case ---- PASSED fixtures/f.py::test_two INFO: TestSetup.ru ---- new test case ---- PASSED ======================= 2 passed in 0.01s =======================

При таком выводе потерялись проценты [ 50%] и [100%].

PyCharm умеет выводить и проценты и stdout. Я пока не научился. Если знаете как это сделать - напишите в телеграм.

Изменим код так, чтобы фикстура использовалась автоматически и выводила на экран имя каждого теста в начале и в конце.

import pytest @pytest.fixture(autouse=True) def announce_new_test(request): print(f"\n\nTEST: {request.node.name}\n{request.node.nodeid}\n") yield print(f"\nEND OF: {request.node.name} {request.node.nodeid}\n\n") def test_one(): assert 1 == 1 def test_two(): assert 2 == 2

============================= test session starts ============================= … collected 2 items fixtures/test_fixt.py::test_one TEST: test_one fixtures/test_fixt.py::test_one PASSED END OF: test_one fixtures/test_fixt.py::test_one fixtures/test_fixt.py::test_two TEST: test_two fixtures/test_fixt.py::test_two PASSED END OF: test_two fixtures/test_fixt.py::test_two ============================== 2 passed in 0.01s ==============================

Изображение баннера

Порядок тестов

Чтобы управлять порядком выполнения тестов нужно установить библиотеку pytest-order

python -m pip install pytest-order

Collecting pytest-order Downloading pytest_order-1.3.0-py3-none-any.whl.metadata (7.7 kB) Requirement already satisfied: pytest>= 6.2.4 in c:\a\pytest\venv\lib\site-packages (from pytest-order) (9.0.1) Requirement already satisfied: colorama>= 0.4 in c:\a\pytest\venv\lib\site-packages (from pytest>= 6.2.4->pytest-order) (0.4.6) Requirement already satisfied: iniconfig>= 1.0.1 in c:\a\pytest\venv\lib\site-packages (from pytest>= 6.2.4->pytest-order) (2.3.0) Requirement already satisfied: packaging>= 22 in c:\a\pytest\venv\lib\site-packages (from pytest>= 6.2.4->pytest-order) (25.0) Requirement already satisfied: pluggy<2,>= 1.5 in c:\a\pytest\venv\lib\site-packages (from pytest>= 6.2.4->pytest-order) (1.6.0) Requirement already satisfied: pygments>= 2.7.2 in c:\a\pytest\venv\lib\site-packages (from pytest>= 6.2.4->pytest-order) (2.19.2) Downloading pytest_order-1.3.0-py3-none-any.whl (14 kB) Installing collected packages: pytest-order Successfully installed pytest-order-1.3.0

С помощью @pytest.mark.order() можно явно указать порядок выполнения теста.

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

import pytest @pytest.mark.order(2) def test_one(): assert 1 == 1 @pytest.mark.order(1) def test_two(): assert 2 == 2

python -m pytest -vv test_order.py

================================= test session starts ================================= platform win32 -- Python 3.12.10, pytest-9.0.1, pluggy-1.6.0 -- C:\A\pytest\venv\Scripts\python.exe cachedir: .pytest_cache rootdir: C:\A\pytest plugins: order-1.3.0 collected 2 items fixtures/test_order.py::test_two PASSED [ 50%] fixtures/test_order.py::test_one PASSED [100%] ================================== 2 passed in 0.01s ==================================

Перезапуск тестов

Для перезапуска упавших тестов нужно установить библиотеку pytest-rerunfailures

python -m pip install pytest-rerunfailures

Collecting pytest-rerunfailures Downloading pytest_rerunfailures-16.1-py3-none-any.whl.metadata (21 kB) Requirement already satisfied: packaging>=17.1 in c:\autotest\sandbox\python\pytest\venv\lib\site-packages (from pytest-rerunfailures) (25.0) Requirement already satisfied: pytest!=8.2.2,>=7.4 in c:\autotest\sandbox\python\pytest\venv\lib\site-packages (from pytest-rerunfailures) (9.0.1) Requirement already satisfied: colorama>=0.4 in c:\autotest\sandbox\python\pytest\venv\lib\site-packages (from pytest!=8.2.2,>=7.4->pytest-rerunfailures) (0.4.6) Requirement already satisfied: iniconfig>=1.0.1 in c:\autotest\sandbox\python\pytest\venv\lib\site-packages (from pytest!=8.2.2,>=7.4->pytest-rerunfailures) (2.3.0) Requirement already satisfied: pluggy<2,>=1.5 in c:\autotest\sandbox\python\pytest\venv\lib\site-packages (from pytest!=8.2.2,>=7.4->pytest-rerunfailures) (1.6.0) Requirement already satisfied: pygments>=2.7.2 in c:\autotest\sandbox\python\pytest\venv\lib\site-packages (from pytest!=8.2.2,>=7.4->pytest-rerunfailures) (2.19.2) Downloading pytest_rerunfailures-16.1-py3-none-any.whl (14 kB) Installing collected packages: pytest-rerunfailures

С помощью @pytest.mark.flaky можно указать сколько раз и с каким интервалом перезапускать тест.

Предположим, мы тестируем приложение, которое медленно загружается.

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

Ожидать 12 секунд каждый раз мы не хотим. Приложение может загрузиться за 6 или даже за 3 секунды.

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

Если оно не загрузилось - это ещё не означает что найден баг. Подождем ещё 3 секунды, потом ещё и так до 12.

Для симуляции такого приложения я взял функцию из статьи «Счётчик вызовов»

# apps.py def slow_app(): slow_app.calls += 1 loaded = 0 if slow_app.calls > 3: loaded = 1 return loaded slow_app.calls = 0

Тест будет выглядеть следующим оригинальным образом

import pytest from apps import slow_app @pytest.mark.flaky(reruns=4, reruns_delay=3) def test_app_is_loaded(): loaded = slow_app() assert loaded == 1

=========================== test session starts ============================ platform win32 -- Python 3.12.10, pytest-9.0.1, pluggy-1.6.0 -- C:\A\pytest\venv\Scripts\python.exe cachedir: .pytest_cache rootdir: C:\A\pytest plugins: order-1.3.0, rerunfailures-16.1 collected 1 item fixtures/test_flaky.py::test_app_is_loaded RERUN [100%] fixtures/test_flaky.py::test_app_is_loaded RERUN [100%] fixtures/test_flaky.py::test_app_is_loaded RERUN [100%] fixtures/test_flaky.py::test_app_is_loaded PASSED [100%] ======================== 1 passed, 3 rerun in 9.05s ========================

Из логов видно, что наше приложение-заглушка «загрузилось» за 9 секуд, что укладывается в 12, поэтому тест прошёл хоть и с тремя перезапусками.

Suite setup и teardown

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

Аналогом того, что в Robot Framework выполняют Suite Setup и Suite Teardown будет одна фикстура, раздлённая на две части с помощью yield.

Пример такой фикстуры, которая импортирует функции start_app() и close_app() и использует их в начале и конце тестовой сессии.

from tests.suite_setup import start_app from tests.suite_teardown import close_app @pytest.fixture(scope="session", autouse=True) def suite_setup_teardown(): start_app() yield close_app()

Автор статьи: Андрей Олегович

Похожие статьи
PyTest
Основы
Параметризация
conftest.py
Удалённый запуск
Ошибки
Видео
Изображение баннера

Поиск по сайту

Подпишитесь на Telegram канал @aofeed чтобы следить за выходом новых статей и обновлением старых

Перейти на канал

@aofeed

Задать вопрос в Телеграм-группе

@aofeedchat

Контакты и сотрудничество:
Рекомендую наш хостинг beget.ru
Пишите на info@urn.su если Вы:
1. Хотите написать статью для нашего сайта или перевести статью на свой родной язык.
2. Хотите разместить на сайте рекламу, подходящую по тематике.
3. Реклама на моём сайте имеет максимальный уровень цензуры. Если Вы увидели рекламный блок недопустимый для просмотра детьми школьного возраста, вызывающий шок или вводящий в заблуждение - пожалуйста свяжитесь с нами по электронной почте
4. Нашли на сайте ошибку, неточности, баг и т.д. ... .......
5. Статьи можно расшарить в соцсетях, нажав на иконку сети: