pytest-django no se limpia entre pruebas cuando se usa Django con múltiples bases de datos. El problema está relacionado con esta pregunta de StackOverflow: http://stackoverflow.com/questions/10121485/django-testcase-not-using-transactions-on-secondary-database
El accesorio db
usa TestCase de Django debajo de las cubiertas.
Aquí está el código del accesorio db
:
case = TestCase(methodName='__init__')
case._pre_setup()
request.addfinalizer(case._post_teardown)
request.addfinalizer(_django_cursor_wrapper.disable)
Así es como lo parcheé como una solución en mi código:
case = TestCase(methodName='__init__')
case.multi_db = True
case._pre_setup()
request.addfinalizer(case._post_teardown)
request.addfinalizer(_django_cursor_wrapper.disable)
Obviamente, esa bandera multi_db existe y por defecto es False por una razón. No estoy seguro de la forma correcta de incorporar el soporte multi_db en pytest_django, y no estoy seguro de la forma correcta de probar dicho cambio.
Si tiene una sugerencia, puedo trabajar en una solicitud de extracción.
¿Podría dar una prueba de ejemplo usando su versión parcheada que usa dos bases de datos? Creo que eso ayudaría a comprender cómo se usan varias bases de datos y qué necesita control sobre otras, luego de configurar multi_db en verdadero.
DATABASES = {
'default': {
'ENGINE' : 'django.db.backends.sqlite3',
'NAME' : 'default.db',
},
'operations': {
'ENGINE' : 'django.db.backends.sqlite3',
'NAME' : 'operations.db',
},
}
La mayoría / todos mis modelos están asociados con las segundas "operaciones" de la base de datos. Creo que TestCase de Django vaciará las transacciones correctamente en la base de datos predeterminada, pero no en otras a menos que multi_db=True
.
Aquí hay algunas pruebas de muestra:
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
Aquí está la salida con mi accesorio db
parcheado, descrito arriba:
$ 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 =====================================
Aquí está la salida con el propio accesorio 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 ===============================
Entonces, de un vistazo rápido, parece que un multi_db
accesorio y pytest.mark.django_db(multi=True)
podrían funcionar como una API. ¿Alguien más tiene mejores ideas?
Eso suena razonable. Ciertamente no tengo otras ideas, mejores o no.
Hola. ¿Hay alguna discusión sobre el soporte de multi_db
además de este tema? Los documentos dicen que uno debe ponerse en contacto, pero ¿cuál es el estado actual de esto?
@slafs No ha habido muchas discusiones / solicitudes para soporte de múltiples bases de datos aparte de esto.
Sería bastante simple pasar multi_db=True
, pero también necesitamos algún tipo de pruebas para esto, por lo que también necesitamos una base de datos secundaria configurada. Si alguien está dispuesto a trabajar en esto, sería genial :)
He estado usando las menciones de @ftobia en la publicación de apertura, pero después de actualizar a Django 1.8.4 desde 1.7 dejó de funcionar.
Hubo un cambio en Django para hacer que la configuración de manejo de código / tearDown buscara un atributo de clase multi_db
lugar de un atributo de instancia.
Cambiar la solución alternativa de
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:
Esto nos impide cambiar del ejecutor de pruebas de Django a pytest.
¿Por qué no usar django_case.multi_db = True
aquí https://github.com/pytest-dev/pytest-django/blob/master/pytest_django/fixtures.py#L107 de forma predeterminada?
+1
+1
Creo que tener un marcador en el marcador django_db como lo haría una buena API:
@pytest.mark.django_db(transactional=True, multi_db=True)
def test_a():
pass
La implementación debería ser relativamente similar a reset_sequences
(PR # 308) o serialized_rollback
(PR # 353).
La parte "complicada" sería mejorar el conjunto de pruebas internas de pytest-django para que contenga múltiples bases de datos.
Si alguien quiere trabajar en esto, seguramente lo revisaré y ayudaré a fusionarlo. ¡Siéntete libre de comenzar a trabajar en un PR si esto es algo que sería interesante para ti!
Dejó algunos comentarios en https://github.com/pytest-dev/pytest-django/pull/397#issuecomment -261987751, y creó un PR, que permitiría cambiar esto de manera genérica: https: // github. com / pytest-dev / pytest-django / pull / 431.
Otra opción para habilitar multi_db para aquellos que buscan una solución temporal. Pon esto en conftest.py
def pytest_sessionstart(session):
from django.test import TransactionTestCase
TransactionTestCase.multi_db = True
Para cualquiera que busque soluciones alternativas, tenga en cuenta que multi_db
quedó obsoleto en Django 2.2. El reemplazo que funcionó para mí es:
TransactionTestCase.databases = set(settings.DATABASES.keys())
@jcushman, ¿te importaría compartir el accesorio completo o el fragmento de código que usaste?
Por alguna razón, pytest_sessionstart()
en nuestra base conftest.py
no estaba haciendo nada. Tomó una página de # 342 junto con pytest estándar monkeypatch
cosas y aterrizó en lo siguiente para nuestro uso de múltiples bases de datos:
# 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()
_Editar: nos trasladamos a un fixture de sesión_
Para no perder los superpoderes de Pytest al cambiar a los casos de prueba de Unittest Django, copié y parcheé los componentes internos de pytest-django en este truco feo (¡pero que funciona!):
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
Con él se puede usar algo como:
class TestCase(object):
def test_ok(
self,
db_case,
...
):
db_case(dbs=('non_default_db_alias',))
Esto debe usarse en lugar de db
fixture o pytest.mark.django_db
mark.
@jcushman, ¿te importaría compartir el accesorio completo o el fragmento de código que usaste?
Agregar respuesta en caso de que alguien más se encuentre con esto.
Debe agregar el código a continuación en conftest.py
. Este dispositivo se selecciona automáticamente, por lo que no es necesario agregarlo a una prueba.
`` py
def pytest_sessionstart (sesión):
desde django.test importar TransactionTestCase
TransactionTestCase.databases = set(settings.DATABASES.keys())
`` ``
Así que creo que este problema se reduce al soporte adecuado de múltiples bases de datos. Cerraré este problema como duplicado.
Comentario más útil
Otra opción para habilitar multi_db para aquellos que buscan una solución temporal. Pon esto en conftest.py