pytest-django ne nettoie pas entre les tests lors de l'utilisation de Django avec plusieurs bases de données. Le problème est lié à cette question StackOverflow : http://stackoverflow.com/questions/10121485/django-testcase-not-using-transactions-on-secondary-database
Le luminaire db
utilise le TestCase de Django sous les couvertures.
Voici le code du projecteur db
:
case = TestCase(methodName='__init__')
case._pre_setup()
request.addfinalizer(case._post_teardown)
request.addfinalizer(_django_cursor_wrapper.disable)
Voici comment je l'ai corrigé comme solution de contournement dans mon code :
case = TestCase(methodName='__init__')
case.multi_db = True
case._pre_setup()
request.addfinalizer(case._post_teardown)
request.addfinalizer(_django_cursor_wrapper.disable)
De toute évidence, cet indicateur multi_db existe et est défini par défaut sur False pour une raison. Je ne suis pas sûr de la bonne façon d'intégrer le support multi_db dans pytest_django, et je ne suis pas sûr de la bonne façon de tester un tel changement.
Si vous avez une suggestion, je peux travailler sur une demande de tirage.
Pourriez-vous donner un exemple de test utilisant votre version corrigée qui utilise deux bases de données ? Je pense que cela aiderait à comprendre comment plusieurs bases de données sont utilisées et ce que vous devez contrôler, puis définir multi_db sur true.
DATABASES = {
'default': {
'ENGINE' : 'django.db.backends.sqlite3',
'NAME' : 'default.db',
},
'operations': {
'ENGINE' : 'django.db.backends.sqlite3',
'NAME' : 'operations.db',
},
}
La plupart / tous mes modèles sont associés à la deuxième base de données "opérations". Je pense que TestCase de Django videra correctement les transactions sur la base de données par défaut, mais pas les autres à moins que multi_db=True
.
Voici quelques exemples de tests :
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
Voici la sortie avec mon appareil db
patché, décrit ci-dessus :
$ 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 =====================================
Voici le résultat avec le propre appareil 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 ===============================
Donc, d'un coup d'œil, il semble qu'un appareil multi_db
et pytest.mark.django_db(multi=True)
pourraient fonctionner comme une API ? Quelqu'un d'autre a-t-il de meilleures idées ?
Cela semble raisonnable. Je n'ai certainement pas d'autres idées, meilleures ou autres.
Bonjour. Y a-t-il une discussion sur la prise en charge de multi_db
plus de ce problème ? Les docs disent que l'on devrait entrer en contact, mais quel est l'état actuel à ce sujet ?
@slafs Il n'y a pas eu beaucoup de discussions/demandes pour la prise en charge de plusieurs bases de données en dehors de cela.
Il serait assez simple de transmettre multi_db=True
, mais nous avons également besoin de tests pour cela, nous avons donc également besoin d'une base de données secondaire. Si quelqu'un est prêt à travailler là-dessus, ce serait génial :)
J'ai utilisé la solution de contournement
Il y a eu une modification de Django pour que la configuration/le démontage de la gestion du code recherche un attribut de classe multi_db
au lieu d'un attribut d'instance.
Changer la solution de @ftobia comme suit a fonctionné pour moi :
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:
Cela nous empêche de passer de Django test runner à pytest.
Pourquoi ne pas utiliser django_case.multi_db = True
ici https://github.com/pytest-dev/pytest-django/blob/master/pytest_django/fixtures.py#L107 par défaut ?
+1
+1
Je pense qu'avoir un marqueur sur le marqueur django_db comme le ferait une bonne API :
@pytest.mark.django_db(transactional=True, multi_db=True)
def test_a():
pass
L'implémentation devrait être relativement similaire à reset_sequences
(PR #308) ou serialized_rollback
(PR #353).
La partie "difficile" serait d'améliorer la suite de tests interne de pytest-django pour contenir plusieurs bases de données.
Si quelqu'un veut travailler dans ce domaine, je le réviserai certainement et l'aiderai à le fusionner. N'hésitez pas à commencer à travailler sur un PR si c'est quelque chose qui pourrait vous intéresser !
A laissé quelques commentaires sur https://github.com/pytest-dev/pytest-django/pull/397#issuecomment -261987751, et a créé un PR, qui permettrait de changer cela de manière générique : https://github. com/pytest-dev/pytest-django/pull/431.
Une autre option pour activer multi_db pour ceux qui recherchent une solution temporaire. Mettez ceci dans conftest.py
def pytest_sessionstart(session):
from django.test import TransactionTestCase
TransactionTestCase.multi_db = True
Pour tous ceux qui recherchent des solutions de contournement, notez que multi_db
était obsolète dans Django 2.2. Le remplacement qui a fonctionné pour moi est:
TransactionTestCase.databases = set(settings.DATABASES.keys())
@jcushman cela vous dérange-
Pour une raison quelconque, pytest_sessionstart()
dans notre base conftest.py
ne faisait rien. J'ai pris une page de #342 avec des trucs standards de pytest monkeypatch
et j'ai atterri sur ce qui suit pour notre utilisation multi-base de données :
# 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 : nous sommes passés à un appareil de session._
Afin de ne pas perdre les super-pouvoirs de pytest en passant aux cas de test unittest Django, j'ai copié et corrigé les composants internes de pytest-django pour ce hack moche (qui fonctionne pourtant !) :
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
Avec lui, on peut utiliser quelque chose comme :
class TestCase(object):
def test_ok(
self,
db_case,
...
):
db_case(dbs=('non_default_db_alias',))
Cela devrait être utilisé à la place de l'appareil db
ou pytest.mark.django_db
marque
@jcushman cela vous dérange-
Ajout d'une réponse au cas où quelqu'un d'autre se heurterait à cela.
Vous devez ajouter le code ci-dessous dans le conftest.py
. Ce luminaire est récupéré automatiquement, il n'est donc pas nécessaire de l'ajouter à un test.
```py
def pytest_sessionstart(session):
de django.test importer TransactionTestCase
TransactionTestCase.databases = set(settings.DATABASES.keys())
````
Je pense donc que ce problème se résume à une prise en charge multi-db appropriée. Je vais clore ce problème en tant que doublon de celui-ci.
Commentaire le plus utile
Une autre option pour activer multi_db pour ceux qui recherchent une solution temporaire. Mettez ceci dans conftest.py