pytest-factoryboy не так выразителен, как я бы sh в подобных случаях. Было бы неплохо вызвать pytest_factoryboy.register
с альтернативным именем для осветителей модели - но, к сожалению, даже если register
принимает параметр _name
, предназначенный для этой цели, _name
игнорируется и вместо него используется underscore(factory_class._meta.model.__name__)
.
К счастью, мы можем обмануть эту логику c, используя желаемое название модели:
@register
class AnyUserFactory(UserFactory):
class Meta:
model = type('AnyUser', (User,), {})
По сути, мы создаем новый подкласс User
с именем AnyUser
, Это приведет к тому, что pytest-factoryboy создаст модель прибора any_user
вместе с any_user__active
, any_user__created
, et c. Теперь, как мы можем параметризовать any_user
для использования UserFactory
, EditorFactory
и AdminFactory
?
К счастью, модели снова работают, запрашивая устройство model_name_factory
с request.getfixturevalue('model_name_factory')
, а не путем прямой ссылки на класс фабрики @register
. В результате мы можем просто переопределить any_user_factory
с любой фабрикой, с которой мы будем sh!
@pytest.fixture(autouse=True, params=[
lazy_fixture('user_factory'),
lazy_fixture('editor_factory'),
lazy_fixture('admin_factory'),
])
def any_user_factory(request):
return request.param
ПРИМЕЧАНИЕ: pytest, кажется, сокращает график доступных приборов на основе аргументов метода теста, а также любые аргументы, запрошенные светильниками. Когда устройство использует request.getfixturevalue
, pytest может сообщить, что не может найти запрошенное устройство - даже если оно четко определено - потому что оно было удалено. Мы передаем autouse=True
нашему устройству, чтобы заставить pytest включить его в граф зависимостей.
Теперь мы можем параметризовать any_user__active
непосредственно в нашем тесте, и any_user
будет User
, Editor
и Admin
с каждым значением active
@pytest.mark.parametrize('any_user__active', [True, False])
def test_some_func(any_user):
print(f'{type(any_user)=} {any_user.active=}')
Какие выходы:
py.test test.py -sq
type(any_user)=<class 'test.User'> any_user.active=True
.type(any_user)=<class 'test.User'> any_user.active=False
.type(any_user)=<class 'test.Editor'> any_user.active=True
.type(any_user)=<class 'test.Editor'> any_user.active=False
.type(any_user)=<class 'test.Admin'> any_user.active=True
.type(any_user)=<class 'test.Admin'> any_user.active=False
.
6 passed in 0.04s
Также, если @pytest.fixture
с request.param
кажется немного многословным, я мог бы предложить использовать pytest-lambda ( заявление об отказе: Я автор). Иногда @pytest.mark.parametrize
может быть ограничивающим или требовать включения дополнительных имен аргументов в методе теста, который go не используется; в этих случаях может быть удобно объявить новые приборы без написания метода полного приспособления.
from pytest_lambda import lambda_fixture
any_user_factory = lambda_fixture(autouse=True, params=[
lazy_fixture('user_factory'),
lazy_fixture('editor_factory'),
lazy_fixture('admin_factory'),
])
@pytest.mark.parametrize('any_user__active', [True, False])
def test_some_func(any_user):
print(f'{type(any_user)=} {any_user.active=}')
Если включение autouse=True
в any_user_factory
является утомительным, потому что это вызывает все другие тесты чтобы быть параметризованным, мы должны найти какой-то другой способ включить any_user_factory
в граф зависимостей pytest.
К сожалению, первый подход, который я попробовал, вызвал ошибки. Я попытался переопределить прибор any_user
, запросив как исходный прибор any_user
, так и наш переопределенный any_user_factory
, как этот
@pytest.fixture
def any_user(any_user, any_user_factory):
return any_user
Увы, pytest не понравился
___________________________ ERROR collecting test.py ___________________________
In test_some_func: function uses no argument 'any_user__active'
К счастью, pytest-lambda предоставляет декоратор для обёртывания функции фикстуры, поэтому аргументы как декорированного метода, так и обёрнутого фиксатора сохраняются. Это позволяет нам явно добавлять any_user_factory
в граф зависимостей
from pytest_lambda import wrap_fixture
@pytest.fixture(params=[ # NOTE: no autouse
lazy_fixture('user_factory'),
lazy_fixture('editor_factory'),
lazy_fixture('admin_factory'),
])
def any_user_factory(request):
return request.param
@pytest.fixture
@wrap_fixture(any_user)
def any_user(any_user_factory, wrapped):
return wrapped() # calls the original any_user() fixture method
ПРИМЕЧАНИЕ: @wrap_fixture(any_user)
напрямую ссылается на метод фикстуры any_user
, определенный pytest_factoryboy при вызове @register. Он будет отображаться как неразрешенная ссылка в большинстве stati c проверок кода / IDE; но до тех пор, пока он появляется после class AnyUserFactory
и в том же модуле, он будет работать.
Теперь, только тесты, запрос которых any_user
попадет на any_user_factory
и получит его параметризацию.
@pytest.mark.parametrize('any_user__active', [True, False])
def test_some_func( any_user):
print(f'{type(any_user)=} {any_user.active=}')
def test_some_other_func():
print('some_other_func')
Выход:
py.test test.py -sq
type(any_user)=<class 'test.User'> any_user.active=True
.type(any_user)=<class 'test.User'> any_user.active=False
.type(any_user)=<class 'test.Editor'> any_user.active=True
.type(any_user)=<class 'test.Editor'> any_user.active=False
.type(any_user)=<class 'test.Admin'> any_user.active=True
.type(any_user)=<class 'test.Admin'> any_user.active=False
.some_other_func
.
7 passed in 0.06 seconds