Как делиться переменной между модулями для всех тестов в py.test

У меня есть несколько тестов, выполняемых py.test, которые расположены в нескольких классах в нескольких файлах.

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

Короче говоря, мне нужно сделать "глобальную переменную" для каждого теста. Вне py.test я не использую эту переменную, поэтому я не хочу ее хранить в тестируемых файлах. Я часто использовал инструменты py.test, но это кажется излишним для этой потребности. Может быть, это единственный способ?

Ответ 1

Обновление: прокрутите вниз до лучшего подхода.

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

import unittest


class BaseTestCase(unittest.TestCase):
    my_dict = {'hello': 'world'}


class TestCase1(BaseTestCase):
    def test_my_dict_key(self):
        self.assertTrue('hello' in self.my_dict)


class TestCase2(BaseTestCase):
    def test_my_dict_value(self):
        self.assertTrue('world' in self.my_dict.values())

Ответ 2

Обновление: крючок пространства имен pytest устарел или удален. Не использовать. Подробнее см. # 3735.

Вы упоминаете очевидный и наименее волшебный вариант: использование прибора. Вы можете применить его ко всем модулям, используя pytestmark = pytest.mark.usefixtures('big_dict') в своем модуле, но тогда это не будет в вашем пространстве имен, поэтому явно запрашивать его может быть лучше.

В качестве альтернативы вы можете назначить вещи в пространство имен pytest с помощью hook:

# conftest.py

def pytest_namespace():
    return {'my_big_dict': {'foo': 'bar'}}

И теперь у вас есть pytest.my_big_dict. Тем не менее, светильник, вероятно, все же приятнее.

Ответ 3

Есть много вещей, которые мне нравятся в py.test, но одна вещь, которую я абсолютно ненавижу, - это то, как плохо она играет с инструментами проверки кода. Я не согласен с тем, что autouse fixture для объявления переменной является "самым ясным" методом в этом случае, потому что это не только полностью сбивает меня с толку, но и всех, кто не знаком с тем, как работает py.test. Там много волшебства, imo.

Итак, одна вещь, которую вы можете сделать, не делает ваш линкер взрывом и не требует, чтобы шаблон TestCase создавал модуль, называемый глобальными. Внутри этого модуля заглушите имена вещей, которые вы хотите глобальным, {} или Нет, и импортируйте глобальный модуль в свои тесты. Затем в файле conftest.py используйте крючки py.test, чтобы установить (или reset) вашу глобальную переменную (ы), если это необходимо. Это имеет то преимущество, что дает вам заглушку для работы при построении тестов и полных данных для тестов во время выполнения.

Например, вы можете использовать hook pytest_configure(), чтобы установить свой dict прямо, когда запускается py.test. Или, если вы хотите удостовериться, что данные были девальвативны между каждым тестом, вы можете авторизовать приспособление, чтобы назначить свою глобальную переменную в ваше известное состояние перед каждым тестом.

# globals.py
my_data = {}  # Create a stub for your variable


# test_module.py
import globals as gbl

def test_foo():
    assert gbl.my_data['foo'] == 'bar'  # The global is in the namespace when creating tests


# conftest.py
import globals as gbl
my_data = {'foo': 'bar'}  # Create the master copy in conftest

@pytest.fixture(autouse=True)
def populate_globals():
    gbl.my_data = my_data  # Assign the master value to the global before each test

Еще одно преимущество этого подхода заключается в том, что вы можете использовать тип hinting в вашем модуле globals, чтобы дать вам завершение кода для глобальных объектов в вашем тесте, что, вероятно, не обязательно для dict, но я считаю это удобным, когда я использую объект (например, webdriver).:)

Ответ 4

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

Итак, вот как я это сделаю: определите autouse fixture, который добавляет ссылку на словарь в глобальном пространстве имен каждого функция.

Вот какой код. Все это в том же файле, но вы можете переместить прибор на conftest.py на верхнем уровне ваших тестов.

import pytest

my_big_global = {'key': 'value'}

@pytest.fixture(autouse=True)
def myglobal(request):
    request.function.func_globals['foo'] = my_big_global

def test_foo():
    assert foo['key'] == 'value'

def test_bar():
    assert foo['key'] == 'bar'

Вот результат, когда я запускаю этот код:

$ py.test test_global.py -vv
======================================= test session starts =======================================
platform darwin -- Python 2.7.5 -- py-1.4.20 -- pytest-2.5.2 -- env/bin/python
collected 2 items

test_global.py:9: test_foo PASSED
test_global.py:12: test_bar FAILED

============================================ FAILURES =============================================
____________________________________________ test_bar _____________________________________________

    def test_bar():
>       assert foo['key'] == 'bar'
E       assert 'value' == 'bar'
E         - value
E         + bar

test_global.py:13: AssertionError
=============================== 1 failed, 1 passed in 0.01 seconds ===============================

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

В заключение, делать что-то подобное, вероятно, плохая идея. Удачи, хотя:)

Ответ 5

Я удивлен, что ни один ответ не упомянул о кешировании: начиная с версии 2.8, pytest обладает мощным механизмом кэширования.

Пример использования

@pytest.fixture(autouse=True)
def init_cache(request):
    data = request.config.cache.get('my_data', None)
    data = {'spam': 'eggs'}
    request.config.cache.set('my_data', data)

Доступ к данным dict в тестах с помощью встроенного request:

def test_spam(request):
    data = request.config.cache.get('my_data')
    assert data['spam'] == 'eggs'

Обмен данными между тестовыми запусками

Самое интересное в request.cache заключается в том, что он сохраняется на диске, поэтому его можно даже разделить между тестовыми запусками. Это удобно, когда вы запускаете тесты, распределенные (pytest-xdist), или имеете некоторое долговременное генерирование данных, которое не изменяется после создания:

@pytest.fixture(autouse=True)
def generate_data(request):
    data = request.config.cache.get('my_data', None)
    if data is None:
        data = long_running_generation_function()
        request.config.cache.set('my_data', data)

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

$ pytest --cache-show
...
my_data contains:
  {'spam': 'eggs'}

Повторите тесты с помощью --cache-clear чтобы удалить кеш и заставить данные пересчитывать. Или просто удалите каталог .pytest_cache в .pytest_cache каталоге проекта.

Куда пойти отсюда

Связанный раздел в документах pytest: Cache: работает с условием cross-testrun.

Ответ 6

Вы можете добавить свою глобальную переменную в качестве опции внутри тэга pytest_addoption. Это можно сделать явно с помощью addoption или использовать метод set_defaults, если вы хотите, чтобы ваш атрибут определялся без проверки командной строки, docs


Когда опция была определена, вы можете вставить ее внутри любого прибора с помощью request.config.getoption, а затем передать его в тест явно или с помощью autouse. Кроме того, вы можете передать свой вариант почти на любой крючок внутри объекта config.

#conftest.py
def pytest_addoption(parser):    
    parser.addoption("--my_global_var", default="foo")
    parser.set_defaults(my_hidden_var="bar")

@pytest.fixture()
def my_hidden_var(request):
    return request.config.getoption("my_hidden_var")

#test.py
def test_my_hidden_var(my_hidden_var):
    assert my_hidden_var == "bar"

Ответ 7

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

образец кода:

@pytest.fixture(scope=session, autouse=True)
def var():
    return {'key':'val'}

def test_var():
    assert var['key']=='val'

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