Pytest-django: Поддержка очистки между тестами с несколькими базами данных

Созданный на 20 мар. 2014  ·  20Комментарии  ·  Источник: pytest-dev/pytest-django

pytest-django не выполняет очистку между тестами при использовании Django с несколькими базами данных. Проблема связана с этим вопросом StackOverflow: http://stackoverflow.com/questions/10121485/django-testcase-not-using-transactions-on-secondary-database

Приспособление db незаметно использует TestCase Django.

Вот код из оригинального прибора db :

case = TestCase(methodName='__init__')
case._pre_setup()
request.addfinalizer(case._post_teardown)
request.addfinalizer(_django_cursor_wrapper.disable)

Вот как я исправил это в качестве обходного пути в моем коде:

case = TestCase(methodName='__init__')
case.multi_db = True
case._pre_setup()
request.addfinalizer(case._post_teardown)
request.addfinalizer(_django_cursor_wrapper.disable)

Очевидно, что флаг multi_db существует и по умолчанию имеет значение False. Я не уверен, как правильно включить поддержку multi_db в pytest_django, и я не уверен, как правильно протестировать такое изменение.

Если у вас есть предложение, я могу поработать над запросом на перенос.

Самый полезный комментарий

Еще один вариант включения multi_db для тех, кто ищет временное решение. Поместите это в conftest.py

def pytest_sessionstart(session):
    from django.test import TransactionTestCase
    TransactionTestCase.multi_db = True

Все 20 Комментарий

Не могли бы вы привести пример теста с использованием вашей исправленной версии, в которой используются две базы данных? Я думаю, это поможет понять, как используются несколько баз данных и что вам нужно контролировать, кроме установки multi_db в true.

DATABASES = {
    'default': {
        'ENGINE'   : 'django.db.backends.sqlite3',
        'NAME'     : 'default.db',
    },
    'operations': {
        'ENGINE'   : 'django.db.backends.sqlite3',
        'NAME'     : 'operations.db',
    },
}

Большинство / все мои модели связаны со вторыми «операциями» базы данных. Я считаю, что TestCase Django будет правильно сбрасывать транзакции в базе данных по умолчанию, но не в других, кроме multi_db=True .

Вот несколько примеров тестов:

from collection.models import Foo

def test_one(db):
    Foo.objects.create()
    assert Foo.objects.count() == 1

def test_two(db):
    assert Foo.objects.count() == 0

Вот результат с моим исправленным прибором db , описанным выше:

$ py.test multi_db_test/ -vv
======================================= test session starts =======================================
platform linux2 -- Python 2.7.2 -- py-1.4.20 -- pytest-2.5.2 -- bin/python
plugins: django
collected 2 items

multi_db_test/test_foo.py:3: test_one PASSED
multi_db_test/test_foo.py:7: test_two PASSED

==================================== 2 passed in 5.75 seconds =====================================

Вот результат использования собственного прибора db pytest_django:

$ py.test multi_db_test/ -vv
======================================= test session starts =======================================
platform linux2 -- Python 2.7.2 -- py-1.4.20 -- pytest-2.5.2 -- bin/python
plugins: django
collected 2 items

multi_db_test/test_foo.py:3: test_one PASSED
multi_db_test/test_foo.py:7: test_two FAILED

============================================ FAILURES =============================================
____________________________________________ test_two _____________________________________________

db = None

    def test_two(db):
>       assert Foo.objects.count() == 0
E       assert 1 == 0
E        +  where 1 = <bound method Manager.count of <django.db.models.manager.Manager object at 0x3cd0890>>()
E        +    where <bound method Manager.count of <django.db.models.manager.Manager object at 0x3cd0890>> = <django.db.models.manager.Manager object at 0x3cd0890>.count
E        +      where <django.db.models.manager.Manager object at 0x3cd0890> = Foo.objects

multi_db_test/test_foo.py:8: AssertionError
=============================== 1 failed, 1 passed in 5.85 seconds ===============================

Итак, на первый взгляд кажется, что приспособление multi_db и pytest.mark.django_db(multi=True) могут работать как API? У кого-нибудь есть идеи получше?

Звучит разумно. У меня, конечно, нет других идей, лучше или нет.

Привет. Есть ли какое-либо обсуждение поддержки multi_db кроме этой проблемы? В документах говорится, что нужно связаться, но каков текущий статус по этому поводу?

@slafs Помимо этого, не было много обсуждений / запросов на поддержку нескольких баз данных.

Было бы довольно просто передать multi_db=True , но нам также нужны какие-то тесты для этого, поэтому нам также нужна настройка вторичной базы данных. Если кто-то захочет над этим поработать, было бы здорово :)

Я использовал обходные пути, упомянутые в первом сообщении

В Django было внесено изменение, чтобы заставить код, обрабатывающий setup / tearDown, искать атрибут multi_db class вместо атрибута экземпляра.

Для меня сработало изменение

case = TestCase(methodName='__init__')
case.__class__.multi_db = True
case._pre_setup()
request.addfinalizer(case._post_teardown)
request.addfinalizer(_django_cursor_wrapper.disable)

+1: +1:

Это мешает нам переключиться с средства запуска тестов Django на pytest.

Почему по умолчанию здесь не используется django_case.multi_db = True https://github.com/pytest-dev/pytest-django/blob/master/pytest_django/fixtures.py#L107 ?

+1

+1

Я думаю, что наличие маркера на маркере django_db хотелось бы у меня хорошего API:

@pytest.mark.django_db(transactional=True, multi_db=True)
def test_a():
    pass

Реализация должна быть относительно похожей на reset_sequences (PR # 308) или serialized_rollback (PR # 353).

«Сложной» частью было бы улучшение внутреннего набора тестов pytest-django, чтобы он содержал несколько баз данных.

Если кто-то захочет поработать с этим, я обязательно его рассмотрю и помогу объединить. Смело начинайте работать над PR, если вам это будет интересно!

Оставил несколько комментариев на https://github.com/pytest-dev/pytest-django/pull/397#issuecomment -261987751 и создал PR, который позволил бы изменить это обычным способом: https: // github. ком / pytest-dev / pytest-django / pull / 431.

Еще один вариант включения multi_db для тех, кто ищет временное решение. Поместите это в conftest.py

def pytest_sessionstart(session):
    from django.test import TransactionTestCase
    TransactionTestCase.multi_db = True

Для всех, кто ищет обходные пути, обратите внимание, что multi_db устарела в Django 2.2. Замена, которая сработала для меня:

TransactionTestCase.databases = set(settings.DATABASES.keys())

@jcushman не могли бы вы поделиться полным

По какой-то причине pytest_sessionstart() в нашей базе conftest.py ничего не делал. Взял страницу из # 342 вместе со стандартным материалом pytest monkeypatch и остановился на следующем для использования с несколькими базами данных:

# OPTION 1: Function fixture; must be included with tests
@pytest.fixture
def django_db_multiple(monkeypatch, request, settings):
    """
    Ensure all test functions using Django test cases have multiple database
    support. This is mostly/only so that Django will wrap ALL database use with
    atomic blocks like it does for DEFAULT_DB_ALIAS.

    https://github.com/django/django/blob/master/django/test/testcases.py#L903
    https://github.com/pytest-dev/pytest-django/issues/76
    """
    from django.test import TestCase
    from django.test import TransactionTestCase

    db_keys = set(settings.DATABASES.keys())

    monkeypatch.setattr(TestCase, 'databases', db_keys)
    monkeypatch.setattr(TransactionTestCase, 'databases', db_keys)

@pytest.mark.django_db
def test_some_test(django_db_multiple):
    pass

# OPTION 2: Session fixture
@pytest.fixture(autouse=True, scope='session')
def django_db_multiple():
    """
    Ensure all test functions using Django test cases have multiple database
    support. This is mostly/only so that Django will wrap ALL database use with
    atomic blocks like it does for DEFAULT_DB_ALIAS.

    https://github.com/django/django/blob/master/django/test/testcases.py#L903
    https://github.com/pytest-dev/pytest-django/issues/76
    https://github.com/pytest-dev/pytest/issues/1872
    """
    from _pytest.monkeypatch import MonkeyPatch
    from django.test import TestCase
    from django.test import TransactionTestCase
    from django.conf import settings

    db_keys = set(settings.DATABASES.keys())

    monkeypatch = MonkeyPatch()
    monkeypatch.setattr(TestCase, 'databases', db_keys)
    monkeypatch.setattr(TransactionTestCase, 'databases', db_keys)

    yield monkeypatch

    monkeypatch.undo()

_Редактировать: мы перешли к фиксации сеанса.

Чтобы не потерять суперсилы pytest, переключившись на тестовые примеры unittest Django, я скопировал и пропатчил внутренности pytest-django в этот уродливый (но работающий!) Хак:

from typing import Optional, Type, TypeVar

import pytest
from django.test import TransactionTestCase
from pytest_django.django_compat import is_django_unittest

TestCase = TypeVar('TestCase', bound=TransactionTestCase)


def _django_db_fixture_helper(
    request, django_db_blocker, transactional: bool = False, reset_sequences: bool = False,
) -> Optional[Type[TestCase]]:
    if is_django_unittest(request):
        return None

    if not transactional and 'live_server' in request.fixturenames:
        # Do nothing, we get called with transactional=True, too.
        return None

    django_db_blocker.unblock()
    request.addfinalizer(django_db_blocker.restore)

    if transactional:
        from django.test import TransactionTestCase as DjangoTestCase  # noqa: WPS433

        if reset_sequences:

            class ResetSequenceTestCase(DjangoTestCase):  # noqa: WPS431
                reset_sequences = True

            DjangoTestCase = ResetSequenceTestCase  # type: ignore[misc] # noqa: N806
    else:
        from django.test import TestCase as DjangoTestCase  # type: ignore[no-redef] # noqa: WPS433

    return DjangoTestCase  # type: ignore[return-value]


@pytest.fixture()
def db_case(request, django_db_setup, django_db_blocker):
    """Require a django test database.

    This database will be setup with the default fixtures and will have
    the transaction management disabled. At the end of the test the outer
    transaction that wraps the test itself will be rolled back to undo any
    changes to the database (in case the backend supports transactions).
    This is more limited than the ``transactional_db`` resource but
    faster.

    If multiple database fixtures are requested, they take precedence
    over each other in the following order (the last one wins): ``db``,
    ``transactional_db``, ``django_db_reset_sequences``.
    """
    if 'django_db_reset_sequences' in request.fixturenames:
        request.getfixturevalue('django_db_reset_sequences')
    if (
        'transactional_db' in request.fixturenames
        or 'live_server' in request.fixturenames
    ):
        request.getfixturevalue('transactional_db')
    else:
        django_case: Optional[Type[TransactionTestCase]] = _django_db_fixture_helper(
            request, django_db_blocker, transactional=False,
        )

        def factory(dbs=None):  # noqa: WPS430
            if django_case is None:
                return
            CaseType: Type[TransactionTestCase] = django_case  # noqa: N806
            if dbs is not None:
                class DatabasesSetTestCase(  # noqa: WPS431
                    CaseType,  # type: ignore[valid-type, misc]
                ):
                    databases = dbs
                CaseType = DatabasesSetTestCase  # noqa: N806
            test_case: TransactionTestCase = CaseType(methodName='__init__')
            test_case._pre_setup()  # type: ignore[attr-defined] # noqa: WPS437
            request.addfinalizer(
                test_case._post_teardown,  # type: ignore[attr-defined] # noqa: WPS437
            )

        return factory

С его помощью можно использовать что-то вроде:

class TestCase(object):

    def test_ok(
        self,
        db_case,
        ...
    ):
        db_case(dbs=('non_default_db_alias',))

Его следует использовать вместо db fixture или pytest.mark.django_db mark.

@jcushman не могли бы вы поделиться полным

Добавление ответа на случай, если кто-то еще столкнется с этим.

Вам нужно добавить приведенный ниже код в conftest.py . Этот прибор подбирается автоматически, поэтому нет необходимости добавлять его в тест.

`` ру
def pytest_sessionstart (сеанс):
из django.test импорт TransactionTestCase

TransactionTestCase.databases = set(settings.DATABASES.keys())

`` ''

Поэтому я считаю, что эта проблема сводится к правильной поддержке нескольких баз данных. Я закрою этот выпуск как дубликат.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги

Смежные вопросы

mpasternak picture mpasternak  ·  5Комментарии

asfaltboy picture asfaltboy  ·  5Комментарии

rodrigorodriguescosta picture rodrigorodriguescosta  ·  4Комментарии

koxu1996 picture koxu1996  ·  3Комментарии

mjk4 picture mjk4  ·  4Комментарии