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

Давайт рассмотрим, как можно при классовой структуре тестов, работать с фикстурами

Как в документации

Например, согласно документации рекомендуется делать вот так:

import pytest

@pytest.fixture
def method_fixture():
    print("method fixture")


@pytest.fixture(scope="class")
def class_fixture():
    print("class fixture")


@pytest.fixture(scope="session")
def session_fixture():
    print("session fixture")


class TestClass:
    def test_1(self, method_fixture, session_fixture):
        pass

    def test_2(self, method_fixture, session_fixture, class_fixture):
        pass
    
    def test_3(self, method_fixture, class_fixture):
        pass

    def test_4(self, class_fixture, session_fixture):
        pass

    def test_5(self, method_fixture, session_fixture):
        pass

Плюсы такого подхода:

  • все согласно документации
  • полная ясность для любого человека, кто прочитал документацию по pytest

Минусы:

  • постоянное перечисление одного и того же
  • при большом количестве испольуземых фикстур, будет много параметров (линтеры могут возмущаться)
  • в различные функции внутри класса придется передавать все фикстуры явно

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

Можно сделать метод, как функцию, у него будет доступ к self, куда можно прилинковать все наши фикстуры:

class TestClassSelfFixtures:
    @pytest.fixture(autouse=True)
    def save_fixture(self, method_fixture, session_fixture, class_fixture):
        self.method_fixture = method_fixture
        self.session_fixture = session_fixture
        self.class_fixture = class_fixture

    def test_6(self):
        self.method_fixture()

    def test_7(self):
        self.class_fixture()

Плюсы:

  • понятно с точки зрения ООП
  • одно место добавления фикстур

Минусы:

  • добавляется еще одна фикстура
  • фикстура будет запускаться на каждую функцию и заново линковать (крошечный, но оверхед)
  • добавленеи новых аттрибутов вне init будет возмущать линтеры
  • все минусы ООП при наследовании

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

Py.test имеет фикстуру request, которая несет в себе некоторые объекты окружения, текущий тест и класс и это можно использовать.

@pytest.fixture(scope="class")
def attach_to_class(request):
    request.cls.class_fixture = class_fixture


@pytest.mark.usefixtures("attach_to_class")
class TestClassRequest:
    def test_8(self):
        print(self.class_fixture)

Здесь у фикстуры request имеется аттрибут cls, который является объектом нашего тестового класса и мы к нему добавляем аттрибут нашей фикстуры. А с помощью @pytest.mark.usefixtures - мы явно указываем список фикстур, которые нам нужны

Плюсы:

  • вызывается один раз на класс
  • вполне pytest way
  • чистый код
  • линтеры не ругаются (особенно если указать аттрибут на уровне класса)

Минусы:

  • чуть сложнее логика, из-за чего требуется быть внимательнее
  • логика спрятана в фикстуру, а не класс

Какой способ использую я

В своей тестовой базе, со временем, мы пришли к последнему способу, но используем его только для class или более высокого уровня фикстур, для scope=“function”, мы явно передаем в каждый тест фикстуру.