Factory Boy 对 pytest 的支持。
项目描述
factory_boy 与 pytest 运行器的集成
pytest-factoryboy 使得将 factory 方法与 dependency 注入相结合变得简单,这是 pytest fixtures 的核心。
安装 pytest-factoryboy
pip install pytest-factoryboy
概念
库导出一个函数,用于注册作为 fixtures 的工厂。fixtures 被贡献到调用注册函数的同一模块。
模型 fixtures
模型 fixture 实现了工厂创建的模型实例。命名约定为模型的下划线小写类名。
import factory
from pytest_factoryboy import register
@register
class AuthorFactory(factory.Factory):
class Meta:
model = Author
name = "Charles Dickens"
def test_model_fixture(author):
assert author.name == "Charles Dickens"
属性是 Fixtures
自动创建了工厂属性的 fixtures。属性名称以模型 fixture 名称和双下划线(类似于 factory_boy 使用的约定)开头。
@pytest.mark.parametrize("author__name", ["Bill Gates"])
def test_model_fixture(author):
assert author.name == "Bill Gates"
多个 fixtures
模型 fixtures 可以用特定的名称进行注册。例如,如果您用“first”、“second”或另一个父对象的名称来引用某些集合的实例
register(AuthorFactory) # author
register(AuthorFactory, "second_author") # second_author
@register # book
@register(_name="second_book") # second_book
@register(_name="other_book") # other_book, book of another author
class BookFactory(factory.Factory):
class Meta:
model = Book
@pytest.fixture
def other_book__author(second_author):
"""Make the relation of the `other_book.author` to `second_author`."""
return second_author
def test_book_authors(book, second_book, other_book, author, second_author):
assert book.author == second_book.author == author
assert other_book.author == second_author
子工厂
子工厂属性指向子工厂的模型 fixture。子工厂的属性作为依赖注入到模型 fixture 中,并且可以通过参数化进行覆盖。
后生成
后生成属性 fixture 仅实现后生成函数的提取值。
工厂 fixture
pytest-factoryboy 还注册了工厂 fixtures,以允许在没有导入它们的情况下使用它们。fixture 名称约定是使用类名的小写下划线形式。
import factory
from pytest_factoryboy import register
class AuthorFactory(factory.Factory):
class Meta:
model = Author
register(AuthorFactory) # => author_factory
def test_factory_fixture(author_factory):
author = author_factory(name="Charles Dickens")
assert author.name == "Charles Dickens"
集成
factory_boy 和 pytest 集成的示例。
# tests/factories.py
import factory
from app import models
from faker import Factory as FakerFactory
faker = FakerFactory.create()
class AuthorFactory(factory.django.DjangoModelFactory):
class Meta:
model = models.Author
name = factory.LazyFunction(lambda: faker.name())
class BookFactory(factory.django.DjangoModelFactory):
class Meta:
model = models.Book
title = factory.LazyFunction(lambda: faker.sentence(nb_words=4))
author = factory.SubFactory(AuthorFactory)
# tests/conftest.py
from pytest_factoryboy import register
from . import factories
register(factories.AuthorFactory)
register(factories.BookFactory)
# tests/test_models.py
from app.models import Book
from .factories import BookFactory
def test_book_factory(book_factory):
"""Factories become fixtures automatically."""
assert book_factory is BookFactory
def test_book(book):
"""Instances become fixtures automatically."""
assert isinstance(book, Book)
@pytest.mark.parametrize("book__title", ["PyTest for Dummies"])
@pytest.mark.parametrize("author__name", ["Bill Gates"])
def test_parametrized(book):
"""You can set any factory attribute as a fixture using naming convention."""
assert book.title == "PyTest for Dummies"
assert book.author.name == "Bill Gates"
Fixture 部分特化
可以通过传递关键字参数来传递,以在 fixture 注册期间覆盖工厂属性值。这在您的测试用例请求大量 fixture 风味时很有用。太多对于常规的 pytest 参数化。在这种情况下,您可以在本地测试模块中注册 fixture 风味,并在 register 函数调用中指定值偏差。
register(AuthorFactory, "male_author", gender="M", name="John Doe")
register(AuthorFactory, "female_author", gender="F")
@pytest.fixture
def female_author__name():
"""Override female author name as a separate fixture."""
return "Jane Doe"
@pytest.mark.parametrize("male_author__age", [42]) # Override even more
def test_partial(male_author, female_author):
"""Test fixture partial specialization."""
assert male_author.gender == "M"
assert male_author.name == "John Doe"
assert male_author.age == 42
assert female_author.gender == "F"
assert female_author.name == "Jane Doe"
Fixture 属性
有时需要将另一个 fixture 的实例作为属性值传递给工厂。可以在需要的位置覆盖生成的属性 fixture,以便可以请求作为 fixture 依赖项的所需值。还有一个用于 fixture 的懒加载包装器,可以在参数化中使用,而不需要在模块中定义 fixtures。
LazyFixture 构造函数接受现有 fixture 名称或带有依赖项的可调用对象
import pytest
from pytest_factoryboy import register, LazyFixture
@pytest.mark.parametrize("book__author", [LazyFixture("another_author")])
def test_lazy_fixture_name(book, another_author):
"""Test that book author is replaced with another author by fixture name."""
assert book.author == another_author
@pytest.mark.parametrize("book__author", [LazyFixture(lambda another_author: another_author)])
def test_lazy_fixture_callable(book, another_author):
"""Test that book author is replaced with another author by callable."""
assert book.author == another_author
# Can also be used in the partial specialization during the registration.
register(BookFactory, "another_book", author=LazyFixture("another_author"))
通用容器类作为模型
通常很有用为 dict 或其他通用容器类创建工厂。在这种情况下,您应该将容器类包装在 named_model(...) 中,以便 pytest-factoryboy 可以在将其用于 SubFactory 或 RelatedFactory 时正确确定模型名称。
否则,pytest-factoryboy 将引发警告。
例如
import factory
from pytest_factoryboy import named_model, register
@register
class JSONPayload(factory.Factory):
class Meta:
model = named_model("JSONPayload", dict)
name = "foo"
def test_foo(json_payload):
assert json_payload.name == "foo"
作为额外的好处,工厂自动注册了 json_payload fixture(而不是 dict),因此无需覆盖 @register(_name="json_payload"))。
后生成依赖项
与 factory_boy 不同,它使用内部容器绑定相关对象以存储懒评估的结果,pytest-factoryboy 依赖于 PyTest 请求。
可以使用后生成挂钩/相关工厂结合传递 SelfAttribute 来解决对象之间的循环依赖关系,但在这种情况下,PyTest 请求 fixture 函数必须返回值,以便在请求中进行缓存并可供其他 fixtures 使用。
这就是为什么pytest-factoryboy中生成后声明的评估被推迟到调用测试函数时才进行。这解决了类似情况下的循环依赖问题。
o->[ A ]-->[ B ]<--[ C ]-o | | o----(C depends on A)----o
另一方面,推迟生成后声明的评估,使得它们的结果在生成非循环依赖的对象时不可用,但这些对象依赖于生成后的操作。
pytest-factoryboy正试图检测循环并自动解决生成后的依赖关系。
from pytest_factoryboy import register
class Foo(object):
def __init__(self, value):
self.value = value
class Bar(object):
def __init__(self, foo):
self.foo = foo
@register
class FooFactory(factory.Factory):
class Meta:
model = Foo
value = 0
@factory.post_generation
def set1(foo, create, value, **kwargs):
foo.value = 1
@register
class BarFactory(factory.Factory):
class Meta:
model = Bar
foo = factory.SubFactory(FooFactory)
@classmethod
def _create(cls, model_class, foo):
assert foo.value == 1 # Assert that set1 is evaluated before object generation
return super(BarFactory, cls)._create(model_class, foo=foo)
# Forces 'set1' to be evaluated first.
def test_depends_on_set1(bar):
"""Test that post-generation hooks are done and the value is 2."""
assert bar.foo.value == 1
钩子
pytest-factoryboy公开了几个pytest钩子,这些钩子可能对例如控制数据库事务、报告等很有帮助。
pytest_factoryboy_done(request) - 在所有基于工厂的固定和它们的生成后操作评估完成后调用。
许可协议
本软件遵循MIT许可证。
© 2015 Oleg Pidsadnyi, Anatoly Bubenkov和其他人
项目详情
下载文件
下载适用于您的平台的文件。如果您不确定选择哪个,请了解更多关于安装包的信息。
源分发
构建分发
pytest_factoryboy-2.7.0.tar.gz的散列
算法 | 散列摘要 | |
---|---|---|
SHA256 | 67fc54ec8669a3feb8ac60094dd57cd71eb0b20b2c319d2957873674c776a77b |
|
MD5 | b0a3278f068b0830698de0955a039eb0 |
|
BLAKE2b-256 | a6bc179653e8cce651575ac95377e4fdf9afd3c4821ab4bba101aae913ebcc27 |
pytest_factoryboy-2.7.0-py3-none-any.whl的散列
算法 | 散列摘要 | |
---|---|---|
SHA256 | bf3222db22d954fbf46f4bff902a0a8d82f3fc3594a47c04bbdc0546ff4c59a6 |
|
MD5 | 5e95a27ccfdea9514b23fbbdaac19fb8 |
|
BLAKE2b-256 | c756d3ef25286dc8df9d1da0b325ee4b1b1ffd9736e44f9b30cfbe464e9f4f14 |