Pytest-django: Prise en charge du nettoyage entre les tests avec plusieurs bases de données

Créé le 20 mars 2014  ·  20Commentaires  ·  Source: pytest-dev/pytest-django

pytest-django ne nettoie pas entre les tests lors de l'utilisation de Django avec plusieurs bases de données. Le problème est lié à cette question StackOverflow : http://stackoverflow.com/questions/10121485/django-testcase-not-using-transactions-on-secondary-database

Le luminaire db utilise le TestCase de Django sous les couvertures.

Voici le code du projecteur db :

case = TestCase(methodName='__init__')
case._pre_setup()
request.addfinalizer(case._post_teardown)
request.addfinalizer(_django_cursor_wrapper.disable)

Voici comment je l'ai corrigé comme solution de contournement dans mon code :

case = TestCase(methodName='__init__')
case.multi_db = True
case._pre_setup()
request.addfinalizer(case._post_teardown)
request.addfinalizer(_django_cursor_wrapper.disable)

De toute évidence, cet indicateur multi_db existe et est défini par défaut sur False pour une raison. Je ne suis pas sûr de la bonne façon d'intégrer le support multi_db dans pytest_django, et je ne suis pas sûr de la bonne façon de tester un tel changement.

Si vous avez une suggestion, je peux travailler sur une demande de tirage.

Commentaire le plus utile

Une autre option pour activer multi_db pour ceux qui recherchent une solution temporaire. Mettez ceci dans conftest.py

def pytest_sessionstart(session):
    from django.test import TransactionTestCase
    TransactionTestCase.multi_db = True

Tous les 20 commentaires

Pourriez-vous donner un exemple de test utilisant votre version corrigée qui utilise deux bases de données ? Je pense que cela aiderait à comprendre comment plusieurs bases de données sont utilisées et ce que vous devez contrôler, puis définir multi_db sur true.

DATABASES = {
    'default': {
        'ENGINE'   : 'django.db.backends.sqlite3',
        'NAME'     : 'default.db',
    },
    'operations': {
        'ENGINE'   : 'django.db.backends.sqlite3',
        'NAME'     : 'operations.db',
    },
}

La plupart / tous mes modèles sont associés à la deuxième base de données "opérations". Je pense que TestCase de Django videra correctement les transactions sur la base de données par défaut, mais pas les autres à moins que multi_db=True .

Voici quelques exemples de tests :

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

Voici la sortie avec mon appareil db patché, décrit ci-dessus :

$ 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 =====================================

Voici le résultat avec le propre appareil 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 ===============================

Donc, d'un coup d'œil, il semble qu'un appareil multi_db et pytest.mark.django_db(multi=True) pourraient fonctionner comme une API ? Quelqu'un d'autre a-t-il de meilleures idées ?

Cela semble raisonnable. Je n'ai certainement pas d'autres idées, meilleures ou autres.

Bonjour. Y a-t-il une discussion sur la prise en charge de multi_db plus de ce problème ? Les docs disent que l'on devrait entrer en contact, mais quel est l'état actuel à ce sujet ?

@slafs Il n'y a pas eu beaucoup de discussions/demandes pour la prise en charge de plusieurs bases de données en dehors de cela.

Il serait assez simple de transmettre multi_db=True , mais nous avons également besoin de tests pour cela, nous avons donc également besoin d'une base de données secondaire. Si quelqu'un est prêt à travailler là-dessus, ce serait génial :)

J'ai utilisé la solution de contournement

Il y a eu une modification de Django pour que la configuration/le démontage de la gestion du code recherche un attribut de classe multi_db au lieu d'un attribut d'instance.

Changer la solution de @ftobia comme suit a fonctionné pour moi :

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:

Cela nous empêche de passer de Django test runner à pytest.

Pourquoi ne pas utiliser django_case.multi_db = True ici https://github.com/pytest-dev/pytest-django/blob/master/pytest_django/fixtures.py#L107 par défaut ?

+1

+1

Je pense qu'avoir un marqueur sur le marqueur django_db comme le ferait une bonne API :

@pytest.mark.django_db(transactional=True, multi_db=True)
def test_a():
    pass

L'implémentation devrait être relativement similaire à reset_sequences (PR #308) ou serialized_rollback (PR #353).

La partie "difficile" serait d'améliorer la suite de tests interne de pytest-django pour contenir plusieurs bases de données.

Si quelqu'un veut travailler dans ce domaine, je le réviserai certainement et l'aiderai à le fusionner. N'hésitez pas à commencer à travailler sur un PR si c'est quelque chose qui pourrait vous intéresser !

A laissé quelques commentaires sur https://github.com/pytest-dev/pytest-django/pull/397#issuecomment -261987751, et a créé un PR, qui permettrait de changer cela de manière générique : https://github. com/pytest-dev/pytest-django/pull/431.

Une autre option pour activer multi_db pour ceux qui recherchent une solution temporaire. Mettez ceci dans conftest.py

def pytest_sessionstart(session):
    from django.test import TransactionTestCase
    TransactionTestCase.multi_db = True

Pour tous ceux qui recherchent des solutions de contournement, notez que multi_db était obsolète dans Django 2.2. Le remplacement qui a fonctionné pour moi est:

TransactionTestCase.databases = set(settings.DATABASES.keys())

@jcushman cela vous dérange-

Pour une raison quelconque, pytest_sessionstart() dans notre base conftest.py ne faisait rien. J'ai pris une page de #342 avec des trucs standards de pytest monkeypatch et j'ai atterri sur ce qui suit pour notre utilisation multi-base de données :

# 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()

_Edit : nous sommes passés à un appareil de session._

Afin de ne pas perdre les super-pouvoirs de pytest en passant aux cas de test unittest Django, j'ai copié et corrigé les composants internes de pytest-django pour ce hack moche (qui fonctionne pourtant !) :

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

Avec lui, on peut utiliser quelque chose comme :

class TestCase(object):

    def test_ok(
        self,
        db_case,
        ...
    ):
        db_case(dbs=('non_default_db_alias',))

Cela devrait être utilisé à la place de l'appareil db ou pytest.mark.django_db marque

@jcushman cela vous dérange-

Ajout d'une réponse au cas où quelqu'un d'autre se heurterait à cela.

Vous devez ajouter le code ci-dessous dans le conftest.py . Ce luminaire est récupéré automatiquement, il n'est donc pas nécessaire de l'ajouter à un test.

```py
def pytest_sessionstart(session):
de django.test importer TransactionTestCase

TransactionTestCase.databases = set(settings.DATABASES.keys())

````

Je pense donc que ce problème se résume à une prise en charge multi-db appropriée. Je vais clore ce problème en tant que doublon de celui-ci.

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