跳转到主要内容

Factory Boy 对 pytest 的支持。

项目描述

factory_boypytest 运行器的集成

https://img.shields.io/pypi/v/pytest-factoryboy.svg https://img.shields.io/pypi/pyversions/pytest-factoryboy.svg https://github.com/pytest-dev/pytest-factoryboy/actions/workflows/main.yml/badge.svg Documentation Status

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 (17.4 kB 查看散列)

上传时间

构建分发

pytest_factoryboy-2.7.0-py3-none-any.whl (16.3 kB 查看散列)

上传时间 Python 3

由以下赞助

AWS AWS 云计算和安全赞助商 Datadog Datadog 监控 Fastly Fastly CDN Google Google 下载分析 Microsoft Microsoft PSF赞助商 Pingdom Pingdom 监控 Sentry Sentry 错误记录 StatusPage StatusPage 状态页面