В материале рассматривается как неявно вызвать фикстуру декоратором @pytest.mark.usefixtures
, от работы которой зависит прохождение теста, а так-же автоматически вызываемые фикстуры фреймворка pytest
.
@pytest.mark.usefixtures
.Бывают ситуации, когда тестовым функциям не требуется прямой доступ к объекту фикстуры и при этом необходимо, чтобы она запускалась в нужное время. Представьте ситуацию, что для работы тестов необходим пустая папка, которую они будут использовать в качестве рабочего каталога. Что бы решить такую задачу, можно использовать стандартный модуль tempfile
и обычную фикстуру pytest
, которая создает пустой каталог. Что бы заставить запускаться такую фикстуру с определенным тестом, можно объявить ее использование с помощью маркера @pytest.mark.usefixtures
перед нужным тестом.
Создадим фикстуру в файл conftest.py
:
# conftest.py import os import shutil import tempfile import pytest @pytest.fixture def cleandir(): old_cwd = os.getcwd() newpath = tempfile.mkdtemp() os.chdir(newpath) yield # очистка директорий после # завершения теста os.chdir(old_cwd) shutil.rmtree(newpath)
Далее объявим использование этой фикстуры в тестовом модуле с помощью маркера @pytest.mark.usefixtures
:
# test_setenv.py import os import pytest # объявим использование фикстуры @pytest.mark.usefixtures("cleandir") class TestDirectoryInit: def test_cwd_starts_empty(self): assert os.listdir(os.getcwd()) == [] with open("myfile", "w") as f: f.write("hello") def test_cwd_again_starts_empty(self): assert os.listdir(os.getcwd()) == []
Декоратор @pytest.mark.usefixtures
заставляет фикстуру cleandir()
инициализироваться для выполнения с каждым тестовым методом класса.
Декоратор @pytest.mark.usefixtures
поддерживает указание несколько фикстур следующим образом:
@pytest.mark.usefixtures("cleandir", "anotherfixture") def test(): ...
Если определить переменную pytestmark
в модуле с тестами, то можно заставить работать такую фикстуры на уровне модуля (т.е. будет выполняться для всех тестов модуля):
pytestmark = pytest.mark.usefixtures("cleandir")
Обратите внимание, что переменная должна называться именно
pytestmark
; если назвать ее по другому, то фикстура инициализироваться не будет.
Можно также заставить фикстуру выполняться для всех тестов проекта, указав в ее ini-файле:
# pytest.ini [pytest] usefixtures = cleandir
Иногда может понадобиться фикстура (или даже несколько), от которой, например, зависят все тесты (т.е. фикстура должна неявно вызываться всеми тестами). Фикстуры, с указанным аргументом autouse=True
- это удобный способ сделать так, чтобы все тесты, расположенные в области действия фикстуры автоматически запрашивали ее. Такое поведение может исключить множество избыточных запросов и даже может обеспечить более расширенное использование фикстуры.
Аutouse-фикстуры соблюдают область действия, определенную с помощью аргумента scope
!
Предположим, есть фикстура, имитирующая базу данных с архитектурой "begin/rollback/commit" и нужно автоматически обернуть каждый тестовый метод транзакцией и откатом к начальному состоянию.
#test_db_transact.py import pytest class DB: def __init__(self): self.intransaction = [] def begin(self, name): self.intransaction.append(name) def rollback(self): self.intransaction.pop() @pytest.fixture(scope="module") def db(): return DB() class TestClass: @pytest.fixture(autouse=True) def transact(self, request, db): db.begin(request.function.__name__) yield db.rollback() def test_method1(self, db): assert db.intransaction == ["test_method1"] def test_method2(self, db): assert db.intransaction == ["test_method2"]
Аutouse-фикстура transact()
уровня класса, это означает, что все тестовые методы класса будут использовать ее без необходимости указывать ее в сигнатуре тестовой функции или применять декоратор @pytest.mark.usefixtures
к классу TestClass.
Фикстуру transact()
можно сделать доступной для всего проекта, не будучи при этом активной. Классический способ сделать это - поместить ее в файл conftest.py
, расположенный на уровне проекта и не указывать аргумент autouse=True
:
# conftest.py @pytest.fixture def transact(request, db): db.begin() yield db.rollback()
И затем, если фикстура понадобится, создать тестовый класс, и объявить ее использование при помощи декоратора @pytest.mark.usefixtures()
:
@pytest.mark.usefixtures("transact") class TestClass: def test_method1(self): ...
Автоматически вызываемые фикстуры (простые фикстуры с включенным аргументом autouse=True
) выполняются первыми в пределах их области их действия.
Будьте осторожны с аutouse-фикстурами, т.к. они будет автоматически выполняться для каждого теста, даже если он этого не запрашивает.
Автоматически вызываемые фикстуры (далее для краткости аutouse-фикстуры) применяются к каждому тесту, следовательно они выполняются перед другими фикстурами в этой области. Фикстуры, запрашиваемые аutouse-фикстурами, фактически сами становятся автоматически используемыми фикстурами для тестов, к которым применяется настоящая аutouse-фикстура.
Таким образом, если a
- это аutouse-фикстура, а фикстура b
- нет, при этом фикстура a
запрашивает фикстуру b
, то фикстура b
также будет автоматически используемой фикстурой, но только для тестов, которые запрашивают фикстуру a
.
import pytest @pytest.fixture(scope="class") def order(): return [] @pytest.fixture(scope="class", autouse=True) def c1(order): order.append("c1") @pytest.fixture(scope="class") def c2(order): order.append("c2") @pytest.fixture(scope="class") def c3(order, c1): order.append("c3") class TestClassWithC1Request: def test_order(self, order, c1, c3): assert order == ["c1", "c3"] class TestClassWithoutC1Request: def test_order(self, order, c2): assert order == ["c1", "c2"]
Несмотря на то, что в классе TestClassWithoutC1Request()
ничего не запрашивает фикстуру c1()
, она все равно выполняется для тестов внутри него. Но только потому, что одна аutouse-фикстура запросила фикстуру, не предназначенную для автоматического использования. Это не означает, что фикстура, не предназначенная для автоматического использования, становится фикстурой автоматического использования для всех контекстов, к которым она может применяться. Она эффективно становится аutouse-фикстура только для контекстов, к которым может применяться настоящая аutouse-фикстура (та, которая запросила фикстуру, не предназначенную для автоматического использования).