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.
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.
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