pytest-2.3: обоснование эволюции фикстур/функаргов

Целевая аудитория: Чтение этого документа требует базовых знаний тестирования на python, методов настройки xUnit и (предыдущего) базового механизма pytest funcarg, см. фанкарги и pytest_funcarg__.. Если вы новичок в pytest, то можете просто проигнорировать этот раздел и прочитать другие разделы.

Недостатки предыдущего механизма pytest_funcarg__

До pytest-2.3 механизм funcarg вызывал фабрику каждый раз, когда требовался funcarg для тестовой функции. Если фабрика хочет повторно использовать ресурс в разных диапазонах, она часто использует помощник request.cached_setup() для управления кэшированием ресурсов. Вот базовый пример того, как мы можем реализовать объект базы данных для каждой сессии:

# content of conftest.py
class Database:
    def __init__(self):
        print("database instance created")

    def destroy(self):
        print("database instance destroyed")


def pytest_funcarg__db(request):
    return request.cached_setup(
        setup=DataBase, teardown=lambda db: db.destroy, scope="session"
    )

Этот подход имеет ряд ограничений и трудностей:

  1. Создание ресурсов funcarg не является простым, вместо этого необходимо разобраться в сложной механике метода cached_setup().

  2. Параметризация ресурса «db» не является простой задачей: необходимо применить декоратор «parametrize» или реализовать хук pytest_generate_tests(), вызывающий parametrize(), который выполняет параметризацию в местах использования ресурса. Более того, необходимо модифицировать фабрику, чтобы использовать параметр extrakey, содержащий request.param для вызова Request.cached_setup.

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

  4. вы никак не можете использовать фабрики funcarg в методах настройки xUnit.

  5. Непараметризованная функция приспособления не может использовать параметризованный ресурс funcarg, если это не указано в сигнатуре тестовой функции.

Все эти ограничения устранены в pytest-2.3 и его улучшенном fixture mechanism.

Прямое масштабирование фабрик приспособлений/функаргов

Вместо того чтобы вызывать cached_setup() с областью действия кэша, вы можете использовать декоратор @pytest.fixture и напрямую указать область действия:

@pytest.fixture(scope="session")
def db(request):
    # factory will only be invoked once per session -
    db = DataBase()
    request.addfinalizer(db.destroy)  # destroy when session is finished
    return db

Этой реализации фабрики больше не нужно вызывать cached_setup(), поскольку она будет вызываться только один раз за сессию. Более того, request.addfinalizer() регистрирует финализатор в соответствии с указанной областью действия ресурса, над которым работает фабричная функция.

Прямая параметризация фабрик ресурсов funcarg

Ранее фабрики funcarg не могли напрямую вызывать параметризацию. Для выполнения параметризации, т.е. вызова теста несколько раз с разными наборами значений, необходимо было указать декоратор @parametrize на тестовой функции или реализовать хук pytest_generate_tests. pytest-2.3 вводит декоратор для использования на самой фабрике:

@pytest.fixture(params=["mysql", "pg"])
def db(request):
    ...  # use request.param

Здесь фабрика будет вызвана дважды (с соответствующими значениями «mysql» и «pg», установленными как атрибуты request.param), и все тесты, требующие «db», также будут запущены дважды. Значения «mysql» и «pg» также будут использоваться для отчетов о вариантах вызова тестов.

Этот новый способ параметризации фабрик funcarg должен во многих случаях позволить повторно использовать уже написанные фабрики, поскольку эффективно request.param уже использовался при параметризации тестовых функций/классов через вызовы metafunc.parametrize(indirect=True).

Конечно, совершенно нормально сочетать параметризацию и скопинг:

@pytest.fixture(scope="session", params=["mysql", "pg"])
def db(request):
    if request.param == "mysql":
        db = MySQL()
    elif request.param == "pg":
        db = PG()
    request.addfinalizer(db.destroy)  # destroy when session is finished
    return db

Это позволит выполнить все тесты, требующие ресурс per-session «db» дважды, получая значения, созданные двумя соответствующими вызовами функции фабрики.

Отсутствие префикса pytest_funcarg__ при использовании декоратора @fixture

При использовании декоратора @fixture имя функции обозначает имя, под которым ресурс может быть доступен в качестве аргумента функции:

@pytest.fixture()
def db(request):
    ...

Имя, под которым может быть запрошен ресурс funcarg, - db.

Вы все еще можете использовать «старый» недекоративный способ указания funcarg-фабрик aka:

def pytest_funcarg__db(request):
    ...

Но тогда невозможно определить масштабирование и параметризацию. Поэтому рекомендуется использовать декоратор factory.

решение проблемы настройки на каждую сессию / автоматические приспособления

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

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

  2. если вы выполняете только сбор (с параметром «–collect-only»), resource-setup все равно будет выполнен.

  3. Если хук pytest_sessionstart содержится в некоторых подкаталогах файла conftest.py, он не будет вызван. Это связано с тем, что данный хук на самом деле используется для создания отчетов, в частности, test-header с информацией о платформе/настройках.

Более того, было нелегко определить scoped setup из плагинов или файлов conftest, кроме как реализовать хук pytest_runtest_setup() и самостоятельно заботиться о scoping/caching. И это практически невозможно сделать с параметризацией, поскольку pytest_runtest_setup() вызывается во время выполнения теста, а параметризация происходит во время сбора.

Из этого следует, что pytest_configure/session/runtest_setup часто не подходят для реализации общих потребностей в фикстурах. Поэтому в pytest-2.3 введены Автоматические светильники (светильники, которые не нужно запрашивать), которые полностью интегрируются с общими fixture mechanism и устаревают многие предыдущие применения крючков pytest.

funcargs/обнаружение приспособлений теперь происходит во время сбора материала

Начиная с pytest-2.3, обнаружение фабрик фикстур/функаргов происходит во время сбора. Это более эффективно, особенно для больших тестовых наборов. Более того, вызов команды «pytest –collect-only» в будущем сможет показать много информации о настройках и, таким образом, представляет собой хороший метод для получения общего представления об управлении фикстурами в вашем проекте.

Заключение и замечания по совместимости

funcargs были первоначально введены в pytest-2.0. В pytest-2.3 механизм был расширен и доработан и теперь описывается как фикстуры:

  • ранее фабрики funcarg указывались со специальным префиксом pytest_funcarg__NAME вместо использования декоратора @pytest.fixture.

  • Фабрики получали объект request, который управлял кэшированием через вызовы request.cached_setup() и позволял использовать другие функарги через вызовы request.getfuncargvalue(). Эти запутанные API затрудняли правильную параметризацию и реализацию кэширования ресурсов. Новый декоратор pytest.fixture() позволяет объявить область видимости и позволить pytest разобраться во всем за вас.

  • Если вы использовали фабрики параметризации и funcarg, которые использовали request.cached_setup(), рекомендуется потратить несколько минут и упростить код функции приспособления, чтобы использовать декоратор Ссылка на светильники вместо него. Это также позволит воспользоваться преимуществами автоматической группировки тестов по ресурсам.

Вернуться на верх