J'ai créé un cas de test simple pour reproduire ce comportement https://github.com/ekiro/case_pytest/blob/master/app/tests.py qui échoue après le deuxième test en utilisant live_server fixture.
Les objets MyModel sont créés lors de la migration, à l'aide de RunPython. Il semble qu'après tout test avec live_server, chaque ligne de la base de données soit tronquée. Postgresql et sqlite3 ont tous deux été testés.
ÉDITER:
Essais
"""
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
C'est à cause du fait que le projecteur transactional_db
est utilisé automatiquement par live_server
(il n'est pas nécessaire de le marquer avec @pytest.mark.django_db
). Il y a plusieurs problèmes/discussions à cet égard, mais la dernière fois que j'y ai regardé de plus près, il n'y avait pas de solution facile à ce problème lié à l'utilisation des migrations de données.
Une solution de contournement consiste à utiliser des appareils pytest pour envelopper vos migrations de données / appareils de données.
(Au fait : il vaut mieux mettre les tests en ligne dans le problème plutôt que dans une ressource externe qui pourrait changer avec le temps.)
Je viens de le frapper aussi et il m'a fallu beaucoup de temps pour le retrouver, même si je suis assez familier avec le framework de test et les migrations de Django.
Il s'agit en fait d'un compromis de performance surprenant de la TestCase
de Django. C'est documenté ici :
https://docs.djangoproject.com/en/1.9/topics/testing/overview/#rollback -emulation
Dans une suite de tests Django classique, la solution de contournement consiste à définir un attribut de classe serialized_rollback = True
sur le cas de test.
Je ne sais pas comment obtenir le même effet avec les classes de test générées dynamiquement par pytest-django.
Le changement suivant « résout » le problème au détriment de la sélection inconditionnelle du comportement le moins efficace.
--- 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 technique suivante fonctionne, mais je ne peux pas la recommander pour des raisons assez évidentes...
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)
Une option d'activation conditionnelle de serialized_rollback
serait-elle une bonne solution ?
C'est-à-dire quelque chose comme
@pytest.mark.django_db(transaction=True, serialized_rollback=True)
def test_foo():
...
Je pense que ce serait bien.
Dans mon cas d'utilisation :
transactional_db
est déclenché par live_server
, mais cela ne me dérangerait pas d'ajouter un appareil django_db
pour spécifier un rollback sérialisécase.serialized_rollback = True
échoue toujours car Django tente de désérialiser les objets dans un ordre qui ne respecte pas les contraintes FK, je pense que c'est un bogue dans DjangoOK parfait :)
Je n'ai pas le temps de travailler sur un correctif pour le moment, mais ajouter une telle option au marqueur django_db
et définir case.serialized_rollback = True
(comme vous l'avez fait ci-dessus) devrait être relativement simple.
Ticket pour le bug Django que j'ai mentionné ci-dessus : https://code.djangoproject.com/ticket/26552
@pelme -- cette pull request implémente-t-elle l'approche "relativement simple" que vous envisagez ?
Merci beaucoup pour le PR. La réalisation a l'air correcte et conforme à ce que j'imaginais. Je n'avais pas pensé à avoir un fixture serialized_rollback mais il est effectivement utile de pouvoir forcer ce comportement depuis d'autres fixtures.
Nous aurions besoin d'un test pour cela aussi, mais l'implémentation semble correcte !
Je vais essayer de trouver le temps de peaufiner le patch (je ne l'ai même pas encore lancé). Je dois également corriger le bogue mentionné dans Django avant de pouvoir l'utiliser.
Version mise à jour du hack ci-dessus pour 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)
Merci pour la mise à jour et désolé de ne pas avoir pu l'intégrer à la 3.0. Je vais essayer d'y arriver pour la prochaine version !
Y a-t-il une chance de le voir dans la prochaine version?
Merci
@Wierrat
Oui probablement.
Pouvez-vous nous aider avec un test pour https://github.com/pytest-dev/pytest-django/pull/353 avant cela, s'il vous plaît ?
Salut les gens,
quel est le dernier statut de ceci? En particulier, j'utilise pytest comme lanceur de test avec la classe StaticLiveServerTestCase de Django. J'ai défini l'attribut de classe serialized_rollback = True
mais cela semble être en vigueur uniquement lors de l'exécution du premier test de la séquence.
Je viens de me faire attraper par celui-ci. En regardant autour, il semble qu'il y ait eu des relations publiques sur cette question, mais aucune d'entre elles ne semble avoir été fusionnée.
Y a-t-il une raison particulière pour laquelle ce problème reste ouvert ?
quelques relations publiques
Probable/uniquement https://github.com/pytest-dev/pytest-django/issues/329 , non ?
La dernière fois que j'ai demandé de l'aide pour un test, il y a plusieurs conflits (peut-être uniquement à cause du noir).
Je suggère toujours à toute personne concernée d'aider avec cela - je ne l'utilise pas moi-même.
Merci pour la réponse rapide @blueyed.
Ma connaissance de pytest / pytest-django est minime mais je vais mettre ça sur ma liste somday/peut-être ! :RÉ
Version mise à jour du hack ci-dessus pour 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')
Je devais dépendre de l'appareil d'origine transactional_db
au lieu de l'appeler car pytest ne permet plus d'appeler directement les fonctions d'appareil. J'obtiens ensuite la définition de l'appareil d'origine et j'y ajoute un finaliseur. Alors que l'insertion à l'index 1 fonctionnait toujours, j'insère avant tous les finaliseurs et utilise django_db_blocker
dans la fonction de restauration car cela semble légèrement moins fragile.
Edit : supprimé l'essai inutile finalement.
@lukaszb
https://github.com/pytest-dev/pytest-django/issues/848
J'utilise cette version de pytest-django (https://github.com/pytest-dev/pytest-django/pull/721/files),
Mais erreur quand même.
Commentaire le plus utile
Je viens de le frapper aussi et il m'a fallu beaucoup de temps pour le retrouver, même si je suis assez familier avec le framework de test et les migrations de Django.
Il s'agit en fait d'un compromis de performance surprenant de la
TestCase
de Django. C'est documenté ici :https://docs.djangoproject.com/en/1.9/topics/testing/overview/#rollback -emulation
Dans une suite de tests Django classique, la solution de contournement consiste à définir un attribut de classe
serialized_rollback = True
sur le cas de test.Je ne sais pas comment obtenir le même effet avec les classes de test générées dynamiquement par pytest-django.