Фикстуры (fixtures) в pytest

Что такое Фикстуры (fixtures) в pytest и как их использовать

Фикстура в pytest — это функция, которая позволяет настроить необходимое состояние теста. Фикстуры могут предоставлять данные, конфигурацию или другие компоненты, чтобы обеспечивать повторное использование кода и упрощать написание тестов.

Пример простой фикстуры:

import pytest

@pytest.fixture
def sample_data():
    data = {"key": "value"}
    return data

def test_sample(sample_data):
    assert sample_data["key"] == "value"

В приведенном примере sample_data — это фикстура. Она передается в функцию теста test_sample() как аргумент.

Встроенные фикстуры

pytest предоставляет ряд встроенных фикстур, которые существенно упрощают написание тестов. Ниже рассмотрены некоторые из наиболее популярных и полезных встроенных фикстур:

  • tmp_path — предоставляет объект пути (из стандартной библиотеки pathlib) к временному каталогу, который уникален для каждой функции теста.
def test_write_file(tmp_path):
    data_file = tmp_path / "data.txt"
    data_file.write_text("Hello, pytest!")
    assert data_file.read_text() == "Hello, pytest!"
  • tmpdir — похож на tmp_path, но предоставляет объекты из библиотеки py.path.
def test_create_directory(tmpdir):
    dir = tmpdir.mkdir("example")
    assert dir.isdir()
  • capsys — позволяет перехватывать вывод в stdout и stderr.
def test_output(capsys):
    print("Hello, pytest!")
    captured = capsys.readouterr()
    assert captured.out == "Hello, pytest!\n"
  • monkeypatch — предоставляет удобные методы для безопасного изменения и восстановления объектов, атрибутов, словарей, переменных окружения и т.д.
def test_environment_variable(monkeypatch):
    monkeypatch.setenv("MY_VAR", "value")
    assert os.environ["MY_VAR"] == "value"
  • pytestconfig — дает доступ к конфигурации pytest, например, к аргументам командной строки или значениям из pytest.ini.
def test_pytestconfig(pytestconfig):
    assert pytestconfig.option.verbose > 0
  • recwarn — Перехватывает предупреждения во время выполнения тестов.
import warnings

def test_warning(recwarn):
    warnings.warn("This is a warning!")
    assert len(recwarn) == 1
    assert str(recwarn[0].message) == "This is a warning!"
  • request — дает информацию о текущем тесте или фикстуре, например, параметры, маркеры и др.
@pytest.mark.my_marker
def test_request_object(request):
    assert request.node.get_closest_marker(name="my_marker") is not None

Это лишь некоторые из встроенных фикстур в pytest. У них есть множество применений, и они часто используются для упрощения процесса написания тестов.

Использование conftest.py

conftest.py — это специальный файл в pytest, который используется для предоставления фикстур, плагинов или другой конфигурации, которые доступны для нескольких тестовых модулей.

Например, если у вас есть фикстура, которую вы хотите использовать в нескольких тестовых файлах, вы можете определить ее в conftest.py, и она будет автоматически доступна для всех тестов.

Пример conftest.py:

import pytest

@pytest.fixture
def common_data():
    data = {"common_key": "common_value"}
    return data

Теперь фикстура common_data доступна для всех тестов, находящихся в той же папке, что и conftest.py, и во всех вложенных папках.

Параметризация

Одной из мощных особенностей pytest является возможность параметризации фикстур. Это позволяет создавать динамические тестовые сценарии на основе набора данных, который предоставляется фикстурой.

Пример:

import pytest

# Определение параметризованной фикстуры
@pytest.fixture(params=[1, 2, 3])
def number_fixture(request):
    return request.param

def test_numbers(number_fixture):
    assert number_fixture in [1, 2, 3]

Здесь у нас есть фикстура number_fixture, которая возвращает три разных значения (1, 2 или 3). Это приведет к тому, что тест test_numbers будет автоматически выполнен три раза с каждым из этих значений.

Использование yield

Фикстуры в pytest могут использовать yield для того, чтобы выполнять код после завершения теста. Это особенно полезно для задач очистки, например, закрытия соединения с базой данных.

Пример:

import pytest

@pytest.fixture
def database_connection():
    conn = connect_to_db()
    yield conn  # Это значение передается тесту
    conn.close()  # Этот код будет выполнен после завершения теста

Использование зависимостей

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

Пример:

@pytest.fixture
def database():
    return Database()

@pytest.fixture
def table(database):
    table = database.create_table("example")
    yield table
    database.drop_table("example")

В примере выше фикстура table зависит от фикстуры database. Когда тест запрашивает table, сначала вызывается database, затем table.

Использование autouse

Фикстура с аргументом autouse=True будет автоматически применяться ко всем тестам в модуле. Вы можете использовать это, чтобы выполнять настройку или очистку для каждого теста без явного указания фикстуры в аргументах теста.

Пример:

import pytest

@pytest.fixture(autouse=True)
def setup_and_teardown():
    # Настройка перед каждым тестом
    print("Setup!")
    yield
    # Очистка после каждого теста
    print("Teardown!")

def test_example_1():
    assert True

def test_example_2():
    assert 2 + 2 == 4

Использование request

Объект request внутри фикстуры позволяет получить информацию о текущем "использующем" тесте. Это может быть полезно, например, для динамического изменения поведения фикстуры на основе атрибутов теста.

Пример:

@pytest.fixture
def example_data(request):
    marker = request.node.get_closest_marker("data_type")
    if marker:
        data_type = marker.args[0]
        if data_type == "type1":
            return {"key": "value1"}
        elif data_type == "type2":
            return {"key": "value2"}
    return {}

@pytest.mark.data_type("type1")
def test_data_type_1(example_data):
    assert example_data["key"] == "value1"

@pytest.mark.data_type("type2")
def test_data_type_2(example_data):
    assert example_data["key"] == "value2"

finalizer

Иногда вам может понадобиться явно добавить функцию завершения к вашей фикстуре. Это можно сделать с помощью request.addfinalizer.

@pytest.fixture
def setup_example(request):
    resource = allocate_resource()

    def fin():
        resource.release()
    request.addfinalizer(fin)

    return resource

Использование params

Помимо параметризации тестов, вы также можете параметризовать фикстуры. С помощью аргумента params вы можете определить набор значений, которые фикстура должна передать тесту.

@pytest.fixture(params=[0, 1, 2])
def number(request):
    return request.param

def test_number(number):
    assert number in [0, 1, 2]

Для чего используется фикстуры

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

Использование фикстур помогает:

  1. Повысить читаемость кода: Ясная структура и разделение ответственности между тестами и их зависимостями делают код более понятным.
  2. Обеспечить изоляцию тестов: В зависимости от выбранной области видимости, фикстуры помогают гарантировать, что каждый тест работает в изолированной среде.
  3. Оптимизировать производительность: Через правильный выбор области видимости, можно избежать избыточной инициализации и уничтожения ресурсов.

Кроме того, благодаря широкому спектру встроенных фикстур, а также возможности кастомизации и расширения, pytest предоставляет тестировщикам все необходимые инструменты для эффективного и качественного тестирования программного обеспечения в различных условиях.

Заключение

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

Содержание: