Pytest-django: Mendukung pembersihan antara pengujian dengan banyak database

Dibuat pada 20 Mar 2014  ·  20Komentar  ·  Sumber: pytest-dev/pytest-django

pytest-Django tidak membersihkan di antara pengujian saat menggunakan Django dengan banyak basis data. Masalahnya terkait dengan pertanyaan StackOverflow ini: http://stackoverflow.com/questions/10121485/Django-testcase-not-using-transactions-on-secondary-database

Perlengkapan db menggunakan TestCase Django di bawah penutup.

Berikut adalah kode dari perlengkapan db :

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

Inilah cara saya menambalnya sebagai solusi dalam kode saya:

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

Jelas bahwa flag multi_db ada dan default ke False karena suatu alasan. Saya tidak yakin cara yang tepat untuk memasukkan dukungan multi_db ke pytest_Django, dan saya tidak yakin cara yang tepat untuk menguji perubahan seperti itu.

Jika Anda memiliki saran, saya dapat mengerjakan permintaan tarik.

Komentar yang paling membantu

Opsi lain untuk mengaktifkan multi_db bagi mereka yang mencari solusi sementara. Letakkan ini di conftest.py

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

Semua 20 komentar

Bisakah Anda memberikan contoh tes menggunakan versi tambalan Anda yang menggunakan dua basis data? Saya pikir itu akan membantu memahami bagaimana banyak basis data digunakan dan apa yang Anda perlu kendalikan selain mengatur multi_db ke true.

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

Sebagian besar/semua model saya dikaitkan dengan "operasi" basis data kedua. Saya percaya TestCase Django akan menyiram transaksi dengan benar pada db default, tetapi tidak yang lain kecuali multi_db=True .

Berikut adalah beberapa contoh tes:

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

Ini adalah output dengan perlengkapan db saya yang ditambal, dijelaskan di atas:

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

Ini adalah output dengan perlengkapan db milik pytest_Django sendiri:

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

Jadi dari tampilan cepat sepertinya perlengkapan multi_db dan pytest.mark.django_db(multi=True) dapat berfungsi sebagai API? Adakah orang lain yang punya ide yang lebih baik?

Itu terdengar masuk akal. Saya tentu tidak punya ide lain, lebih baik atau sebaliknya.

Halo. Apakah ada diskusi tentang dukungan multi_db selain masalah ini? Dokumen mengatakan bahwa seseorang harus menghubungi tetapi apa statusnya saat ini?

@slafs Belum banyak diskusi/permintaan untuk dukungan multi database selain ini.

Akan sangat mudah untuk meneruskan multi_db=True , tetapi kami juga memerlukan beberapa jenis tes untuk ini, jadi kami juga memerlukan pengaturan database sekunder. Jika ada yang mau mengerjakan ini, itu akan sangat bagus :)

Saya telah menggunakan work-around @ftobia yang disebutkan di posting pembuka, tetapi setelah memutakhirkan ke Django 1.8.4 dari 1.7 berhenti bekerja.

Ada perubahan Django untuk membuat pengaturan penanganan kode/tearDown mencari atribut kelas multi_db alih-alih atribut instans.

Mengubah solusi @ftobia menjadi yang berikut ini berhasil untuk saya:

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:

Ini menghentikan kami dari beralih dari pelari uji Django ke pytest.

Mengapa tidak menggunakan django_case.multi_db = True sini https://github.com/pytest-dev/pytest-Django/blob/master/pytest_django/fixtures.py#L107 secara default?

+1

+1

Saya pikir memiliki penanda pada penanda Django_db seperti itu akan menjadi API yang baik:

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

Implementasinya harus relatif mirip dengan reset_sequences (PR #308) atau serialized_rollback (PR #353).

Bagian "rumit" adalah meningkatkan rangkaian pengujian internal pytest-Django untuk memuat banyak basis data.

Jika seseorang ingin bekerja dalam hal ini, saya pasti akan meninjaunya dan membantu menggabungkannya. Jangan ragu untuk mulai mengerjakan PR jika ini adalah sesuatu yang menarik bagi Anda!

Meninggalkan beberapa komentar di https://github.com/pytest-dev/pytest-Django/pull/397#issuecomment -261987751, dan membuat PR, yang memungkinkan untuk mengubah ini secara umum: https://github. com/pytest-dev/pytest-Django/pull/431.

Opsi lain untuk mengaktifkan multi_db bagi mereka yang mencari solusi sementara. Letakkan ini di conftest.py

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

Untuk orang lain yang mencari solusi, perhatikan bahwa multi_db tidak digunakan lagi di Django 2.2. Pengganti yang berhasil untuk saya adalah:

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

@jcushman apakah Anda keberatan membagikan perlengkapan lengkap atau cuplikan kode yang Anda gunakan?

Untuk alasan apa pun, pytest_sessionstart() di basis kami conftest.py tidak melakukan apa-apa. Mengambil halaman dari #342 bersama dengan standar pytest monkeypatch barang dan telah mendarat di berikut ini untuk penggunaan multi-database kami:

# 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: kami pindah ke perlengkapan sesi._

Agar tidak kehilangan kekuatan super pytest dengan beralih ke kasus uji Django unittest, saya menyalin dan menambal internal pytest-Django ke peretasan jelek (belum berfungsi!) ini:

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

Dengan itu seseorang dapat menggunakan sesuatu seperti:

class TestCase(object):

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

Ini harus digunakan sebagai ganti perlengkapan db atau tanda pytest.mark.django_db .

@jcushman apakah Anda keberatan membagikan perlengkapan lengkap atau cuplikan kode yang Anda gunakan?

Menambahkan balasan jika ada orang lain yang menabrak ini.

Anda perlu menambahkan kode di bawah ini ke dalam conftest.py . Perlengkapan ini diambil secara otomatis sehingga tidak perlu menambahkannya ke tes.

```py
def pytest_sessionstart(sesi):
dari django.test impor TransactionTestCase

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

````

Jadi saya percaya masalah ini bermuara pada dukungan multi-db yang tepat. Saya akan menutup masalah ini sebagai duplikat dari itu.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat