Pytest-django: Unterstützen Sie die Bereinigung zwischen Tests mit mehreren Datenbanken

Erstellt am 20. März 2014  ·  20Kommentare  ·  Quelle: pytest-dev/pytest-django

pytest-django räumt zwischen den Tests nicht auf, wenn Django mit mehreren Datenbanken verwendet wird. Das Problem hängt mit dieser StackOverflow-Frage zusammen: http://stackoverflow.com/questions/10121485/django-testcase-not-using-transactions-on-secondary-database

Das db Fixture verwendet Djangos TestCase unter dem Deckmantel.

Hier ist der Code aus dem ursprünglichen db Fixture:

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

So habe ich es als Workaround in meinem Code gepatcht:

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

Offensichtlich existiert dieses multi_db-Flag und ist aus einem bestimmten Grund standardmäßig auf False gesetzt. Ich bin mir nicht sicher, wie die multi_db-Unterstützung in pytest_django richtig integriert werden soll, und ich bin mir nicht sicher, wie eine solche Änderung richtig getestet werden soll.

Wenn Sie einen Vorschlag haben, kann ich an einem Pull-Request arbeiten.

Hilfreichster Kommentar

Eine weitere Option zum Aktivieren von multi_db für diejenigen, die nach einer temporären Lösung suchen. Setzen Sie dies in conftest.py

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

Alle 20 Kommentare

Könnten Sie einen Beispieltest mit Ihrer gepatchten Version geben, die zwei Datenbanken verwendet? Ich denke, das würde helfen zu verstehen, wie mehrere Datenbanken verwendet werden und was Sie steuern müssen, wenn Sie multi_db auf true setzen.

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

Die meisten / alle meine Modelle sind mit der zweiten Datenbank "Operationen" verknüpft. Ich glaube, Djangos Testfall wird Transaktionen in der Standard-DB korrekt leeren, andere jedoch nicht, es sei denn, multi_db=True .

Hier einige Beispieltests:

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

Hier ist die Ausgabe mit meinem gepatchten db Fixture, wie oben beschrieben:

$ 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 =====================================

Hier ist die Ausgabe mit pytest_djangos eigenem db Fixture:

$ 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 ===============================

Auf den ersten Blick sieht es so aus, als ob ein multi_db Fixture und pytest.mark.django_db(multi=True) als API funktionieren könnten? Hat noch jemand bessere Ideen?

Das klingt vernünftig. Ich habe sicherlich keine anderen Ideen, besser oder anders.

Hallo. Gibt es außer diesem Problem noch eine Diskussion über den multi_db Support? In der Dokumentation steht , dass man sich melden sollte, aber wie ist der aktuelle Stand dazu?

@slafs Abgesehen davon gab es nicht viele Diskussionen/Anfragen zur Unterstützung

Es wäre ziemlich einfach, multi_db=True weiterzugeben, aber wir brauchen dafür auch einige Tests, also müssen wir auch eine sekundäre Datenbank einrichten. Wenn jemand bereit ist, daran zu arbeiten, wäre das großartig :)

Ich habe die Workaround- Erwähnungen von

Es gab eine Django- Änderung , damit die Codebehandlung setup/tearDown nach einem multi_db Klassenattribut anstelle eines Instanzattributs sucht.

Das Ändern der Problemumgehung von @ftobia in Folgendes hat bei mir funktioniert:

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:

Dies hindert uns daran, vom Django-Testläufer zu pytest zu wechseln.

Warum nicht standardmäßig django_case.multi_db = True hier https://github.com/pytest-dev/pytest-django/blob/master/pytest_django/fixtures.py#L107 verwenden ?

+1

+1

Ich denke, dass ein Marker auf dem django_db-Marker wie eine gute API wäre:

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

Die Implementierung sollte reset_sequences (PR #308) oder serialized_rollback (PR #353) relativ ähnlich sein.

Der "knifflige" Teil wäre, die interne Testsuite von pytest-django so zu verbessern, dass sie mehrere Datenbanken enthält.

Wenn jemand daran arbeiten möchte, werde ich es auf jeden Fall überprüfen und bei der Zusammenführung helfen. Fühlen Sie sich frei, mit der Arbeit an einer PR zu beginnen, wenn dies etwas für Sie ist!

Hinterließ einige Kommentare unter https://github.com/pytest-dev/pytest-django/pull/397#issuecomment -261987751 und erstellte eine PR, die es ermöglichen würde, dies auf allgemeine Weise zu ändern: https://github. com/pytest-dev/pytest-django/pull/431.

Eine weitere Option zum Aktivieren von multi_db für diejenigen, die nach einer temporären Lösung suchen. Setzen Sie dies in conftest.py

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

Für alle anderen, die nach Problemumgehungen suchen, beachten Sie, dass multi_db in Django 2.2 veraltet ist. Der Ersatz, der bei mir funktioniert hat, ist:

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

@jcushman macht es Ihnen etwas aus, das vollständige Gerät oder den Code-Snippet zu teilen, den Sie verwendet haben?

Aus welchem ​​Grund auch immer, pytest_sessionstart() in unserer Basis conftest.py tat nichts. Ich habe eine Seite von #342 zusammen mit Standard-Pytest monkeypatch Zeug genommen und bin auf folgendem für unsere Multi-Datenbank-Nutzung gelandet:

# 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()

_Edit: Wir sind zu einem Session-Fixture umgezogen._

Um die Superkräfte von pytest nicht zu verlieren, indem ich zu Unittest-Django-Testfällen wechselte, habe ich pytest-django-Interna in diesen hässlichen (aber funktionierenden!) Hack kopiert und gepatcht:

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

Damit kann man etwas verwenden wie:

class TestCase(object):

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

Dies sollte anstelle von db Fixture oder pytest.mark.django_db Marke verwendet werden.

@jcushman macht es Ihnen etwas aus, das vollständige Gerät oder den Code-Snippet zu teilen, den Sie verwendet haben?

Antwort hinzufügen, falls jemand anderes darauf stößt.

Sie müssen den folgenden Code in das conftest.py einfügen. Dieses Gerät wird automatisch aufgenommen, sodass es nicht zu einem Test hinzugefügt werden muss.

```py
def pytest_sessionstart(session):
aus django.test import TransactionTestCase

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

````

Ich glaube also, dass dieses Problem auf die richtige Multi-DB-Unterstützung zurückzuführen ist. Ich schließe dieses Thema als Duplikat davon.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen

Verwandte Themen

jedie picture jedie  ·  7Kommentare

mjk4 picture mjk4  ·  4Kommentare

AndreaCrotti picture AndreaCrotti  ·  5Kommentare

asfaltboy picture asfaltboy  ·  5Kommentare

tolomea picture tolomea  ·  4Kommentare