J'ai une situation où j'ai besoin de charger un énorme appareil db qui est créé par une fonction. L'appareil est nécessaire dans tous les tests API. J'ai donc créé un appareil de session à conftest.py
qui le ferait. Mais le problème est pytest
lance l'exception suivante même si j'ai marqué django_db
:
E Failed: Database access not allowed, use the "django_db" mark to enable it.
Ci-dessous mon extrait de code.
from permission.helpers import update_permissions
pytestmark = [
pytest.mark.django_db(transaction = True),]
@pytest.fixture(scope="session", autouse = True)
def permission(request):
load_time_consuming_db_fixture()
@ludbek, nous avons également manqué une telle fonctionnalité et avons créé un plugin pour cette fonctionnalité :
https://github.com/tipsi/pytest-tipsi-django (utilisation : https://github.com/tipsi/pytest-tipsi-django/blob/master/test_django_plugin/app/tests/test_transactions.py)
En collaboration avec :
https://github.com/tipsi/pytest-tipsi-testing
Il vous donne la possibilité d'imbriquer les transactions et de corriger l'ordre d'exécution/d'arrêt.
@cybergrind Merci d'avoir répondu. Je vais certainement vérifier et vous faire savoir comment ça s'est passé.
Cela a été le plus gros problème pour moi venant des tests basés sur les classes UnitTest de Django, vers pytest-django - dans Django, nous utilisons setUpTestData
pour exécuter des opérations de base de données coûteuses une fois (équivalent aux appareils pytest à portée de session), et puis il y a une astuce pour exécuter obj.refresh_from_db()
dans le setUp
pour actualiser les références de classe.
Même si je ne fais que créer une instance de modèle de base de données et que je la recharge à chaque TC, c'est presque toujours plus rapide que de créer dans chaque cas de test.
Ce serait formidable si nous pouvions fusionner en amont les appareils de portée de session de pytest-tipsi-django, si c'est une possibilité; il a fallu creuser un peu pour trouver ce problème et sa solution.
salut @paultiplady
Je ne suis pas sûr que l'approche de pytest-tipsi-djanjo
corresponde au modèle de test habituel pour pytest
. La différence la plus notable est la finition des appareils : actuellement, pytest
ne termine pas explicitement les appareils inutiles avec une portée plus large. Vous devez donc terminer explicitement les transactions dans un ordre particulier et, en général, cela peut entraîner des effets très différents (actuellement, pytest
peut garder les appareils actifs même si le test actif et ses appareils ne l'exigent pas du tout).
Nous avons dû changer les tests de notre projet pour cette approche car nous devons parfois tester de grands scénarios et nous avons remplacé la gestion manuelle des transactions existante dans des tests énormes avec des montages légèrement meilleurs, mais cela nécessite toujours une attention particulière sur les tests de commande.
À l'heure actuelle, je ne vois qu'une seule solution pour cela : mettre une sorte de FAQ dans la documentation.
Merci pour le détail supplémentaire @cybergrind. J'ai creusé un peu plus cela, mais je n'ai plus de temps pour aujourd'hui - voici où je dois en venir, j'apprécierais une vérification de bon sens pour savoir si cette approche est utile ou non, puisque je suis pas si familier avec les composants internes de pytest.
De plus, je ne comprends pas ce que vous entendez par "pytest ne termine pas explicitement les appareils inutiles avec une portée plus large", pourriez-vous développer un peu plus s'il vous plaît? Cela fait-il référence aux finaliseurs ? Cela pourrait affecter ce que j'ai écrit ci-dessous.
Le plugin pytest-django utilise la marque django_db
, qui est gérée dans _django_db_marker dans plugin.py (https://github.com/pytest-dev/pytest-django/blob/master/pytest_django/plugin.py #L375), appelant le luminaire db
à portée de fonction (https://github.com/pytest-dev/pytest-django/blob/master/pytest_django/fixtures.py#L142). Ce luminaire instancie un Django TestCase et appelle sa fonction _pre_setup
(et met en file d'attente le _post_teardown
).
Je peux voir quelques options :
Je me demande si nous pourrions étendre _django_db_marker
pour éventuellement faire une classe ou une session
configuration étendue également, qui ferait essentiellement la même chose que db
, mais en appelant l'équivalent d'un cls.setUpTestData
ou d'une fonction passée dans la marque kwargs à la place.
Pour la portée de classe, et je suppose également pour la portée de session, le comportement attendu serait de restaurer la base de données par la suite, nous avons donc essentiellement besoin de luminaires de portée qui se déclenchent dans le bon ordre, et que chacun configure son propre atomique transaction. Je pense que cela signifie qu'il doit s'agir d'une modification du luminaire db
, plutôt que d'un luminaire séparé qui fonctionne à côté.
De cette façon, nous déclencherions correctement toute configuration de niveau classe/session spécifiée, et cette configuration serait appelée une fois par classe/session. Je pense qu'une modification de la marque elle-même est nécessaire car si vous venez de configurer une fonction de portée de session qui déclenche manuellement django_db_blocker.unblock()
, cela semble se produire après que la marque django_db a configuré la première transaction.
Cela pourrait ressembler à ceci (dans plugin.py _django_db_marker()
):
if marker:
if marker.session_db:
getfixturevalue(request, 'db_session')
if marker.class_db:
getfixturevalue(request, 'db_class')
validate_django_db(marker)
if marker.transaction:
getfixturevalue(request, 'transactional_db')
else:
getfixturevalue(request, 'db')
Est-ce un discours fou ou ce fil mérite-t-il d'être exploré plus avant?
Concernant la finalisation : https://github.com/tipsi/pytest-tipsi-testing/blob/master/tests/test_finalization.py
Ce test ne fonctionne pas sans finalisation explicite, comme pour les appareils de base de données de niveau non fonctionnel. Et il s'agit de l'implémentation de pytest, il n'y a donc rien à faire dans pytest-django pour le réparer.
Double de #388 et #33
Merci, je termine.
👍 merci !
J'apprécierais également beaucoup cette fonctionnalité.
@mkokotovich
Combien? Assez pour le réaliser vous-même ? ;)
Quoi qu'il en soit, le problème principal / fondamental ici (d'après le commentaire d'origine) est déjà que la base de données est réinitialisée pendant les tests, il n'y a donc pas de moyen trivial d'avoir un appareil à portée de session comme celui-ci.
Ce qui pourrait fonctionner, c'est quelque chose:
@pytest.fixture(scope="session")
def django_db_setup(django_db_setup, django_db_blocker):
with django_db_blocker.unblock():
with transaction.atomic(): # XXX: could/should use `TestCase_enter_atomics` for multiple dbs
load_time_consuming_db_fixture()
yield
L'idée étant de tout envelopper dans un bloc atomique supplémentaire. Cependant, cela n'a pas été testé et vous devrez peut-être utiliser TransactionTestCase
pour cela.
@paultiplady
Votre commentaire sonne bien - c'est-à-dire qu'il vaut la peine d'être approfondi AFAICS (voir aussi mon commentaire précédent).
@blueyed, nous utilisons une telle approche depuis plus d'un an (tout emballer dans un bloc atomique supplémentaire), cela fonctionne plutôt bien.
Mais vous ne pouvez pas simplement déposer quelque chose comme ça parce que pytest n'a pas le déterministe où le niveau de la session (et autre supérieur au test) sera fermé, il faudra donc suivre les dépendances des transactions db, qu'elles aient besoin les unes des autres ou pas directement .
Vous devez donc suivre explicitement la pile des transactions et fermer les transactions imbriquées avant le prochain test. Cela peut être fait de cette manière : https://github.com/tipsi/pytest-tipsi-django/blob/master/pytest_tipsi_django/django_fixtures.py#L46
désolé, je tiens à préciser car je crois que je rencontre le même problème:
Si je veux créer un appareil qui repose sur la création d'un nouvel objet db, je pensais pouvoir le faire
@pytest.mark.django_db(transaction=True)
@pytest.fixture(scope="session")
def object():
object = Object.create_object(params)
yield object
// or alternatively
object = mixer.blend('myObject')
yield object
Cependant, je reçois l'erreur suivante lors de l'exécution du cas de test : Database access not allowed, use the "django_db" mark, or the "db" or "transactional_db" fixtures to enable it.
Commentaire le plus utile
Cela a été le plus gros problème pour moi venant des tests basés sur les classes UnitTest de Django, vers pytest-django - dans Django, nous utilisons
setUpTestData
pour exécuter des opérations de base de données coûteuses une fois (équivalent aux appareils pytest à portée de session), et puis il y a une astuce pour exécuterobj.refresh_from_db()
dans lesetUp
pour actualiser les références de classe.Même si je ne fais que créer une instance de modèle de base de données et que je la recharge à chaque TC, c'est presque toujours plus rapide que de créer dans chaque cas de test.
Ce serait formidable si nous pouvions fusionner en amont les appareils de portée de session de pytest-tipsi-django, si c'est une possibilité; il a fallu creuser un peu pour trouver ce problème et sa solution.