Pytest-django: Comment utiliser la marque django_db lors de la fixation de la session ?

Créé le 14 sept. 2017  ·  15Commentaires  ·  Source: pytest-dev/pytest-django

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()
db-configuration enhancement

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é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.

Tous les 15 commentaires

@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.

33 est fermé, et #388 ne contient aucune discussion significative (contrairement à celle-ci). Il semble étrange de fermer celui-ci @blueyed , si quoi que ce soit, je suggérerais de fermer # 388 et de faire de celui-ci le ticket canonique pour ce problème.

👍 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.

Cette page vous a été utile?
0 / 5 - 0 notes