pytest-factoryboy 中具有多 table 继承的 Django 模型的参数化属性
Parametrize attributes of Django-models with multi-table inheritance in pytest-factoryboy
我正在使用 Django
并想使用 pytest
、pytest-django
、pytest-factoryboy
和 pytest-lazyfixtures
.
编写测试
我有使用多table 继承的 Django 模型,如下所示:
class User(models.Model):
created = models.DateTimeField()
active = models.BooleanField()
class Editor(User):
pass
class Admin(User):
pass
我也为所有模型创建了工厂并注册了,如:
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = User
created = ... # some datetime
active = factory.Faker("pybool")
class EditorFactory(UserFactory):
class Meta:
model = Editor
...
现在我想测试一个函数,它可以将 User
、Editor
或 Admin
中的任何一个作为输入,并使用所有用户类型和 [= 的变体对测试进行参数化23=] 和 created
,就像这样(不幸的是它不能那样工作):
@pytest.mark.parametrize("any_user", [lazy_fixture("user"), lazy_fixture("editor"), lazy_fixture("admin")])
@pytest.mark.parametrize("any_user__active", [True, False])
def test_some_func(any_user):
... # test some stuff
但是 In test_some_func: function uses no argument 'any_user__active'
失败了。
知道如何最好地解决这个问题吗?
我当然可以这样做,但不是很好:
@pytest.mark.parametrize("any_user", [lazy_fixture("user"), lazy_fixture("editor"), lazy_fixture("admin")])
@pytest.mark.parametrize("active", [True, False])
def test_some_func(any_user, active):
any_user.active = active
# save any_user if necessary
... # test some stuff
有更好的建议吗?
However that fails with In test_some_func: function uses no argument
'any_user__active'.
这是因为您还没有将此 any_user__active
作为参数传递给测试函数。
所以将您的测试文件更改为
def test_some_func(any_user__active, any_user):
例子如下
@pytest.mark.parametrize("days, expected", [
(-1, 0),
(1, 1),
(0, 0),
(365, 365)
])
def test_subscription_to_for_user(days, expected):
在这种情况下,pytest-factoryboy 的表现力不如我希望的那样。用模型夹具的备用名称调用 pytest_factoryboy.register
会很好——但不幸的是,即使 register
采用用于此目的的 _name
参数,_name
也会被忽略,并使用 underscore(factory_class._meta.model.__name__)
代替。
谢天谢地,我们可以欺骗这个逻辑来使用我们想要的模型名称:
@register
class AnyUserFactory(UserFactory):
class Meta:
model = type('AnyUser', (User,), {})
本质上,我们创建了一个名为 AnyUser
的 User
的新子 class。这将导致 pytest-factoryboy 创建 any_user
模型夹具,以及 any_user__active
、any_user__created
等。现在,我们如何参数化 any_user
以使用 UserFactory
、EditorFactory
和AdminFactory
?
再次谢天谢地,模型夹具通过使用 request.getfixturevalue('model_name_factory')
请求 model_name_factory
夹具来工作,而不是通过直接引用 @register
的工厂 class。结果是我们可以简单地用我们想要的任何工厂覆盖 any_user_factory
!
@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
可能会受到限制,或者可能需要在未使用的测试方法中包含额外的参数名称;在这些情况下,无需编写完整的 fixture 方法就可以方便地声明新的 fixture。
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=}')
如果在 any_user_factory
上包含 autouse=True
很麻烦,因为它会导致所有其他测试被参数化,我们必须找到其他方法将 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 提供了一个装饰器来包装 fixture 函数,因此被装饰的方法和包装的 fixture 的参数都被保留了下来。这允许我们显式地将 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)
在调用@register时直接引用了pytest_factoryboy定义的any_user
fixture方法。它会在大多数静态代码检查器/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
我正在使用 Django
并想使用 pytest
、pytest-django
、pytest-factoryboy
和 pytest-lazyfixtures
.
我有使用多table 继承的 Django 模型,如下所示:
class User(models.Model):
created = models.DateTimeField()
active = models.BooleanField()
class Editor(User):
pass
class Admin(User):
pass
我也为所有模型创建了工厂并注册了,如:
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = User
created = ... # some datetime
active = factory.Faker("pybool")
class EditorFactory(UserFactory):
class Meta:
model = Editor
...
现在我想测试一个函数,它可以将 User
、Editor
或 Admin
中的任何一个作为输入,并使用所有用户类型和 [= 的变体对测试进行参数化23=] 和 created
,就像这样(不幸的是它不能那样工作):
@pytest.mark.parametrize("any_user", [lazy_fixture("user"), lazy_fixture("editor"), lazy_fixture("admin")])
@pytest.mark.parametrize("any_user__active", [True, False])
def test_some_func(any_user):
... # test some stuff
但是 In test_some_func: function uses no argument 'any_user__active'
失败了。
知道如何最好地解决这个问题吗?
我当然可以这样做,但不是很好:
@pytest.mark.parametrize("any_user", [lazy_fixture("user"), lazy_fixture("editor"), lazy_fixture("admin")])
@pytest.mark.parametrize("active", [True, False])
def test_some_func(any_user, active):
any_user.active = active
# save any_user if necessary
... # test some stuff
有更好的建议吗?
However that fails with In test_some_func: function uses no argument 'any_user__active'.
这是因为您还没有将此 any_user__active
作为参数传递给测试函数。
所以将您的测试文件更改为
def test_some_func(any_user__active, any_user):
例子如下
@pytest.mark.parametrize("days, expected", [
(-1, 0),
(1, 1),
(0, 0),
(365, 365)
])
def test_subscription_to_for_user(days, expected):
在这种情况下,pytest-factoryboy 的表现力不如我希望的那样。用模型夹具的备用名称调用 pytest_factoryboy.register
会很好——但不幸的是,即使 register
采用用于此目的的 _name
参数,_name
也会被忽略,并使用 underscore(factory_class._meta.model.__name__)
代替。
谢天谢地,我们可以欺骗这个逻辑来使用我们想要的模型名称:
@register
class AnyUserFactory(UserFactory):
class Meta:
model = type('AnyUser', (User,), {})
本质上,我们创建了一个名为 AnyUser
的 User
的新子 class。这将导致 pytest-factoryboy 创建 any_user
模型夹具,以及 any_user__active
、any_user__created
等。现在,我们如何参数化 any_user
以使用 UserFactory
、EditorFactory
和AdminFactory
?
再次谢天谢地,模型夹具通过使用 request.getfixturevalue('model_name_factory')
请求 model_name_factory
夹具来工作,而不是通过直接引用 @register
的工厂 class。结果是我们可以简单地用我们想要的任何工厂覆盖 any_user_factory
!
@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
可能会受到限制,或者可能需要在未使用的测试方法中包含额外的参数名称;在这些情况下,无需编写完整的 fixture 方法就可以方便地声明新的 fixture。
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=}')
如果在 any_user_factory
上包含 autouse=True
很麻烦,因为它会导致所有其他测试被参数化,我们必须找到其他方法将 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 提供了一个装饰器来包装 fixture 函数,因此被装饰的方法和包装的 fixture 的参数都被保留了下来。这允许我们显式地将 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)
在调用@register时直接引用了pytest_factoryboy定义的any_user
fixture方法。它会在大多数静态代码检查器/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