Creé un caso de prueba simple para reproducir este comportamiento https://github.com/ekiro/case_pytest/blob/master/app/tests.py que falla después de la segunda prueba usando el accesorio live_server.
Los objetos MyModel se crean en la migración, utilizando RunPython. Parece que después de cualquier prueba con live_server, todas las filas de la base de datos se truncan. Se probaron tanto postgresql como sqlite3.
EDITAR:
Pruebas
"""
MyModel objects are created in migration
Test results:
app/tests.py::test_no_live_server PASSED
app/tests.py::test_live_server PASSED
app/tests.py::test_live_server2 FAILED
app/tests.py::test_no_live_server_after_live_server FAILED
"""
import pytest
from .models import MyModel
@pytest.mark.django_db()
def test_no_live_server():
"""Passed"""
assert MyModel.objects.count() == 10
@pytest.mark.django_db()
def test_live_server(live_server):
"""Passed"""
assert MyModel.objects.count() == 10
@pytest.mark.django_db()
def test_live_server2(live_server):
"""Failed, because count() returns 0"""
assert MyModel.objects.count() == 10
@pytest.mark.django_db()
def test_no_live_server_after_live_server():
"""Failed, because count() returns 0"""
assert MyModel.objects.count() == 10
Esto se debe a que el accesorio transactional_db
está siendo utilizado automáticamente por live_server
(no es necesario marcarlo con @pytest.mark.django_db
). Hay varios temas / discusiones a este respecto, pero la última vez que miré más de cerca, no había una solución fácil para este problema que viene con el uso de migraciones de datos.
Una solución alternativa es usar accesorios de pytest para envolver sus migraciones de datos / accesorios de datos.
(Por cierto: es mejor poner las pruebas en línea en el problema en lugar de en un recurso externo que podría cambiar con el tiempo).
Acabo de llegar a esto también y me tomó mucho tiempo rastrearlo, a pesar de que estoy razonablemente familiarizado con el marco de prueba y las migraciones de Django.
Esta es en realidad una compensación de rendimiento sorprendente de TestCase
de Django. Está documentado aquí:
https://docs.djangoproject.com/en/1.9/topics/testing/overview/#rollback -emulation
En un conjunto de pruebas normal de Django, la solución consiste en establecer un atributo de clase serialized_rollback = True
en el caso de prueba.
No sé cómo lograr el mismo efecto con las clases de prueba generadas dinámicamente de pytest-django.
El siguiente cambio "resuelve" el problema a expensas de seleccionar incondicionalmente el comportamiento menos eficiente.
--- pytest_django/fixtures.py.orig 2016-04-27 17:12:25.000000000 +0200
+++ pytest_django/fixtures.py 2016-04-27 17:21:50.000000000 +0200
@@ -103,6 +103,7 @@
if django_case:
case = django_case(methodName='__init__')
+ case.serialized_rollback = True
case._pre_setup()
request.addfinalizer(case._post_teardown)
La siguiente técnica funciona, pero no puedo recomendarla por razones bastante obvias ...
import pytest
from django.core.management import call_command
from pytest_django.fixtures import transactional_db as _transactional_db
def _reload_fixture_data():
fixture_names = [
# Create fixtures for the data created by data migrations
# and list them here.
]
call_command('loaddata', *fixture_names)
@pytest.fixture(scope='function')
def transactional_db(request, _django_db_setup, _django_cursor_wrapper):
"""
Override a pytest-django fixture to restore the contents of the database.
This works around https://github.com/pytest-dev/pytest-django/issues/329 by
restoring data created by data migrations. We know what data matters and we
maintain it in (Django) fixtures. We don't read it from the database. This
causes some repetition but keeps this (pytest) fixture (almost) simple.
"""
try:
return _transactional_db(request, _django_db_setup, _django_cursor_wrapper)
finally:
# /!\ Epically shameful hack /!\ _transactional_db adds two finalizers:
# _django_cursor_wrapper.disable() and case._post_teardown(). Note that
# finalizers run in the opposite order of that in which they are added.
# We want to run after case._post_teardown() which flushes the database
# but before _django_cursor_wrapper.disable() which prevents further
# database queries. Hence, open heart surgery in pytest internals...
finalizers = request._fixturedef._finalizer
assert len(finalizers) == 2
assert finalizers[0].__qualname__ == 'CursorManager.disable'
assert finalizers[1].__qualname__ == 'TransactionTestCase._post_teardown'
finalizers.insert(1, _reload_fixture_data)
¿Sería una buena solución una opción para habilitar condicionalmente serialized_rollback
?
Es decir, algo como
@pytest.mark.django_db(transaction=True, serialized_rollback=True)
def test_foo():
...
Creo que eso estaría bien.
En mi caso de uso:
transactional_db
es activado por live_server
, pero no me importaría agregar un accesorio django_db
para especificar la reversión serializadacase.serialized_rollback = True
todavía falla porque Django intenta objetos deserializados en un orden que no respeta las restricciones de FK, creo que eso es un error en DjangoOK perfecto :)
No tengo tiempo para trabajar en una solución en este momento, pero agregar esa opción al marcador django_db
debería y configurar case.serialized_rollback = True
(como lo hizo anteriormente) debería ser relativamente sencillo.
Ticket para el error de Django que mencioné anteriormente: https://code.djangoproject.com/ticket/26552
@pelme : ¿esta solicitud de extracción implementa el enfoque "relativamente sencillo" que está imaginando?
Muchas gracias por las relaciones públicas. La implementación parece correcta y lo que imaginé. No pensé en tener un dispositivo serialized_rollback, pero de hecho es útil poder forzar este comportamiento desde otros dispositivos.
Necesitaríamos una prueba para esto también, ¡pero la implementación parece correcta!
Intentaré encontrar tiempo para pulir el parche (aún no lo he ejecutado). Necesito arreglar el error mencionado en Django antes de poder usar esto.
Versión actualizada del truco anterior para pytest-django ≥ 3.0:
@pytest.fixture(scope='function')
def transactional_db(request, django_db_setup, django_db_blocker):
"""
Override a pytest-django fixture to restore the contents of the database.
This works around https://github.com/pytest-dev/pytest-django/issues/329 by
restoring data created by data migrations. We know what data matters and we
maintain it in (Django) fixtures. We don't read it from the database. This
causes some repetition but keeps this (pytest) fixture (almost) simple.
"""
try:
return _transactional_db(request, django_db_setup, django_db_blocker)
finally:
# /!\ Epically shameful hack /!\ _transactional_db adds two finalizers:
# django_db_blocker.restore() and test_case._post_teardown(). Note that
# finalizers run in the opposite order of that in which they are added.
# We want to run after test_case._post_teardown() flushes the database
# but before django_db_blocker.restore() prevents further database
# queries. Hence, open heart surgery in pytest internals...
finalizers = request._fixturedef._finalizer
assert len(finalizers) == 2
assert finalizers[0].__qualname__ == '_DatabaseBlocker.restore'
assert finalizers[1].__qualname__ == 'TransactionTestCase._post_teardown'
finalizers.insert(1, _reload_fixture_data)
Gracias por la actualización y lo siento, no pude poner esto en 3.0. ¡Intentaré llegar a esto para el próximo lanzamiento!
¿Hay alguna posibilidad de verlo en la próxima versión?
Gracias
@Wierrat
Si probablemente.
¿Puede ayudarnos con una prueba para https://github.com/pytest-dev/pytest-django/pull/353 antes de eso, entonces, por favor?
Hola amigos,
¿Cuál es el estado más reciente de esto? En particular, estoy usando pytest como mi corredor de pruebas con la clase StaticLiveServerTestCase de Django. Establecí el atributo de clase serialized_rollback = True
pero eso parece estar en efecto solo cuando se ejecuta la primera prueba de la secuencia.
Solo me atrapó este. Mirando a su alrededor, parece que ha habido algunas relaciones públicas sobre este tema, pero ninguno de ellos parece haberse fusionado.
¿Hay alguna razón en particular por la que este tema permanezca abierto?
algunas relaciones públicas
Probablemente / solo https://github.com/pytest-dev/pytest-django/issues/329 , ¿no?
La última vez que he pedido ayuda con una prueba, tiene varios conflictos (tal vez solo debido al negro).
Todavía sugiero a cualquier persona afectada que ayude con eso, no lo estoy usando yo mismo.
Gracias por la rápida respuesta @blueyed.
¡Mi conocimiento de pytest / pytest-django es mínimo, pero lo pondré en mi lista de somday / tal vez! :D
Versión actualizada del truco anterior para pytest 5.2.1, pytest-django 3.5.1:
from functools import partial
@pytest.fixture
def transactional_db(request, transactional_db, django_db_blocker):
# Restore DB content after all of transactional_db's finalizers have
# run. Finalizers are run in the opposite order of that in which they
# are added, so we prepend the restore to the front of the list.
#
# Works for pytest 5.2.1, pytest-django 3.5.1
restore = partial(_restore_db_content, django_db_blocker)
finalizers = request._fixture_defs['transactional_db']._finalizers
finalizers.insert(0, restore)
# Simply restoring after yielding transactional_db wouldn't work because
# it would run before transactional_db's finalizers which contains the truncate.
return transactional_db
def _restore_db_content(django_db_fixture, django_db_blocker):
with django_db_blocker.unblock():
call_command('loaddata', '--verbosity', '0', 'TODO your fixture')
Tuve que depender del dispositivo transactional_db
lugar de llamarlo, ya que pytest ya no permite llamar directamente a las funciones del dispositivo. Luego obtengo la definición de accesorio del accesorio original y le agrego un finalizador. Si bien la inserción en el índice 1 aún funcionaba, inserto antes de todos los finalizadores y uso django_db_blocker
en la función de restauración, ya que parece un poco menos frágil.
Editar: eliminó el intento innecesario finalmente.
@lukaszb
https://github.com/pytest-dev/pytest-django/issues/848
Yo uso esta versión de pytest-django (https://github.com/pytest-dev/pytest-django/pull/721/files),
Pero sigue siendo un error.
Comentario más útil
Acabo de llegar a esto también y me tomó mucho tiempo rastrearlo, a pesar de que estoy razonablemente familiarizado con el marco de prueba y las migraciones de Django.
Esta es en realidad una compensación de rendimiento sorprendente de
TestCase
de Django. Está documentado aquí:https://docs.djangoproject.com/en/1.9/topics/testing/overview/#rollback -emulation
En un conjunto de pruebas normal de Django, la solución consiste en establecer un atributo de clase
serialized_rollback = True
en el caso de prueba.No sé cómo lograr el mismo efecto con las clases de prueba generadas dinámicamente de pytest-django.