Pytest-django: دعم قواعد البيانات المتعددة (متعدد ديسيبل)

تم إنشاؤها على ٧ مايو ٢٠٢١  ·  16تعليقات  ·  مصدر: pytest-dev/pytest-django

يحل هذا العدد محل بعض القضايا التاريخية: # 76 ، # 342 ، # 423 ، # 461 ، # 828 ، # 838 ، # 839 (ربما تكون قائمة جزئية).

خلفية

يدعم Django قواعد البيانات المتعددة . هذا يعني تحديد إدخالات متعددة في إعداد DATABASE ، والذي يسمح بعد ذلك باستعلامات معينة مباشرة لقواعد بيانات معينة.

إحدى الحالات هي عندما تكون قاعدة البيانات الإضافية مستقلة تمامًا ، ولها عمليات ترحيل وإعدادات خاصة بها وما إلى ذلك.

الحالة الثانية هي عندما تكون قاعدة البيانات الإضافية للقراءة فقط ، وتستخدم فقط لاستعلامات القراءة ، ولا تتم إدارتها بواسطة Django.

الحالة الثالثة هي نسخة متماثلة للقراءة فقط ، لهذا يوفر Django الإعداد MIRROR

يسمح Django بتكوين الترتيب الذي يتم به إعداد قواعد بيانات الاختبار.

دعم اختبار Django متعدد ديسيبل

يعتمد pytest-django في الغالب على فئات Django الأساسية TransactionTestCase و TestCase للتعامل مع إعدادات DB وما شابه. يتم تشغيل كل اختبار pytest-django في TestCase / TransactionTestCase الذي يتم إنشاؤه ديناميكيًا.

الإعداد الرئيسي لدعم mutli-db هو TransactionTestCase.databases . هذا يخبر Django بقواعد البيانات التي يجب مراعاتها في حالة الاختبار. بشكل افتراضي هو فقط default . من الممكن تحديد __all__ لتضمين كل قواعد البيانات.

ملاحظة تاريخية: تمت إضافة السمة TransactionTestCase.databases في Django 2.2. قبل ذلك تم استخدام سمة multi_db . pytest-django يدعم فقط Django> = 2.2 لذلك لا داعي لأن نهتم بذلك.

المحاولات السابقة

397 - يضيف multi_db=True وسيطة إلى pytest.mark.django_db() ، ويضيف django_multi_db fixture. المشكلة: استخدام السمة القديمة multi_db بدلاً من السمة databases .

416 - تشبه إلى حد بعيد # 397.

431 - تضيف تركيبات django_db_testcase والتي تسمح للمستخدم بتخصيص فئة حالة الاختبار بالكامل ، بما في ذلك إعداد databases . تم رفضه لكونه مرنًا للغاية ، فأنا أفضل الدعم المباشر متعدد ديسيبل.

896 - يضيف إعدادًا عامًا لكل قاعدة بيانات لتحديد ما إذا كان سيتم الإضافة إلى قيمة databases أم لا. تم الرفض لأنني أعتقد أنه يجب أن يكون من الممكن تخصيص كل اختبار.

الحل المقترح

IMO نريد شيئًا مثل # 397 / # 416 ، ولكن تم تحديثه لاستخدام databases بدلاً من multi_db . سيكون جزء التثبيت مشكلة بعض الشيء لأنه لم يعد مجرد عنصر منطقي (تم تمكين / عدم تمكين التثبيت) ، بل قائمة بأسماء قواعد البيانات المستعارة. لذلك ستكون هناك حاجة إلى بعض الحلول لذلك ، أو ربما سيتم دعم العلامة فقط.

سأحاول العمل على ذلك بنفسي ، ولكن إذا لم أفعل ذلك لسبب ما ، فموظفو العلاقات العامة مرحب بهم بالتأكيد!

enhancement

التعليق الأكثر فائدة

لذلك عثرت للتو على هذا القسم من المستندات. نقوم حاليًا بترقية مشروع DB Django متعدد من Django 1.11 إلى Django 3.2 وأيضًا ترقية حزم pytest و pytest-django. لم أكن على علم بكل هذه التغييرات ، لكن بالنسبة لنا ، فقد نجحت في الخروج من الصندوق دون أي مشاكل. الاختبارات تمر دون مشاكل. لذا شكرا لكم على ذلك!

ال 16 كومينتر

تتبع هذا! إنه أداة توقف بالنسبة لنا ، حيث يمنع التحديثات التي تتجاوز Django 3.0. لدينا حاليًا حل داخلي لإصلاح القرود يعمل مع Django <= 3.0 ولكن لا يمكنني تشغيله عند إزالة المعلمة multi_db.

العلاقات العامة الأولية بالرقم 930.

jgb https://github.com/pytest-dev/pytest-django/pull/397 تم تطويره لحالة الاستخدام الخاصة بك مرة أخرى في اليوم :) لقد عفا عليه الزمن الآن: zoidberg:

bluetech أختبر 4.3.0 خصيصًا لدعم متعدد ديسيبل. يبدو أنه يعمل ، نوع من. على الأقل تحت django 3.0 يعمل.
في الواقع ، يحدث خطأ مع جميع إصدارات django: 3.0 و 3.1 و 3.2.
مثلما أقوم بإجراء اختبار يقوم بإنشاء كائن في قاعدة البيانات ، وعندما أقوم بإجراء الاختبار مرة أخرى ، لا يزال كائن التشغيل السابق موجودًا.
أي فكرة عما يمكن أن يحدث خطأ هنا؟

يبدو أن هذه المشكلة القديمة تصف ما أراه: https://github.com/pytest-dev/pytest-django/issues/76
4.3.0 يسمح لي بالوصول إلى عدة ديسيبلات ، لكنه لا يقوم بتنظيفها / مسحها بشكل صحيح بين عمليات التشغيل التجريبية.

مدهش! شكرا لعملك على هذا ، يبدو رائعا.

لقد قمت بتحويل مشروع متعدد الوسائط إلى واجهة برمجة التطبيقات التجريبية ويبدو أنه يعمل ، بما في ذلك مسح البيانات بين عمليات التشغيل التجريبية. (كنت أستخدم سابقًا الحل البديل TransactionTestCase.databases = TestCase.databases = set(settings.DATABASES.keys()) في جلسة محددة النطاق.)

إنه يعمل أيضًا بشكل صحيح مع pytest-xdist ، وهو أمر رائع جدًا.

سيكون جزء التثبيت مشكلة بعض الشيء لأنه لم يعد مجرد عنصر منطقي (تم تمكين / عدم تمكين التثبيت) ، بل قائمة بأسماء قواعد البيانات المستعارة. لذلك ستكون هناك حاجة إلى بعض الحلول لذلك ، أو ربما سيتم دعم العلامة فقط.

FWIW لقد كافحت بالتأكيد مع عدم وجود نسخة ثابتة. فقط لتوضيح المشكلة التي تتحدث عنها ، باستخدام مشروع أحادي ديسيبل ، سأفعل شيئًا كالتالي:

@pytest.fixture
def person(db):
    return Person.objects.create(...)

# no need for pytest.mark.django_db:
def test_person(person):
    ...

مع متعدد ديسيبل أتخيل الرغبة في القيام بشيء مثل هذا:

@pytest.fixture
def db_people(add_django_db):
    add_django_db('people')

@pytest.fixture
def db_books(add_django_db):
    add_django_db('books')

@pytest.fixture
def person(db_people):
    return Person.objects.create(...)

@pytest.fixture
def book(db_books):
    return Book.objects.create(...)

# no need for @pytest.mark.django_db(databases=['people', 'books'])
def test_reader(person, book):
    ...

(لست متأكدًا مما إذا كان ذلك ممكنًا للتنفيذ ، ولكن كمثال على كيفية عمل واجهة برمجة التطبيقات.)

سيكون هذا مناسبًا للتركيبات بشكل عام ، لأنه بخلاف ذلك ، من السهل نسيان إزالة 'books' من databases عندما تقوم بتعديل التركيبات المستخدمة بواسطة test_reader لاحقًا ، وهي أيضًا رائعة فقط لإزالة خط من الضوضاء من كل اختبار. لكنها تصبح مفيدة بشكل خاص عند استخدام العقيدة:

def reader_stuff():
    """
    # no way to use pytest.mark here?
    >>> person = getfixture("person")
    >>> book = getfixture("book")
    ...
    """

كان الحل البديل الذي وجدته لتشغيل أطروحاتي هو إضافة أداة تثبيت آلية ، لذا فإن تركيبات db و transactional_db ستحمّل بشكل افتراضي جميع قواعد البيانات ، ما لم يكن هناك pytest.mark.django_db صريحًا:

@pytest.fixture(autouse=True)
def database_defaults(request):
    # set default databases for `db` and `transactional_db` fixtures
    if not hasattr(request.node, '_pytest_django_databases'):
        request.node._pytest_django_databases = list(settings.DATABASES.keys())

هذا شيء مفيد جدًا لتكون قادرًا على الاشتراك ، لذلك قد يكون من الجيد أن يكون لديك طريقة رسمية أكثر للقيام بذلك؟ لكنه لا يزال يترك المذاهب أقل كفاءة مما يمكن أن تكون عليه بخلاف ذلك ، نظرًا لأنها لا تحتاج في الواقع إلى الوصول إلى جميع قواعد البيانات ، لذلك أعتقد أن إصدارًا ثابتًا سيظل مفيدًا.

كانت لدي فكرة أخرى أثناء إجراء التحويل - سيكون الأمر رائعًا إذا كان هناك بعض العلامات التي يمكنني استخدامها لتحذير من قواعد البيانات غير المستخدمة ، لذلك إذا قمت بإزالة تركيبات book من test_reader I سيتم تحذيرك من أن 'books' لم يعد مطلوبًا في التعليق التوضيحي. لست متأكدًا مما إذا كان من الممكن تتبع ذلك ، فقط عصف ذهني عشوائي في حالة وجود طريقة سهلة لتنفيذه.

شكرا مرة أخرى لدفع هذا إلى الأمام!

bluetech فلماذا الفلاش بين كل اختبار يعمل مع jcushman ولكن ليس بالنسبة لي؟ :سؤال:

jgbjcushman شكرا للتقارير ! سأبحث في هذا مرة أخرى في نهاية هذا الأسبوع وسأرد بعد ذلك.

jgb هذا ما أستخدمه بنجاح في حال كان ذلك مفيدًا.

الحزم:

Python 3.7.10
Postgres 11.11
Django==3.2.3
pytest==6.0.1
pytest-django==4.3.0
pytest-xdist==1.32.0

قواعد بيانات:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        ...
    },
    'capdb': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        ...
    }

لقد بدأت بإضافة ملف اختبار بسيط فقط قبل تحويل اختباراتي الفعلية:

@pytest.fixture
def court():
    return Court.objects.create(name='foo')  # comes from capdb

@pytest.fixture
def mailing_list():
    return MailingList.objects.create(email='[email protected]')  # comes from default

@pytest.mark.parametrize("x", range(10))
@pytest.mark.django_db(databases=['default', 'capdb'])
def test_multi(x, court, mailing_list):
    assert Court.objects.count() == 1
    assert MailingList.objects.count() == 1

نتائج:

# pytest capdb/tests/test_multi.py
========================================= test session starts =========================================
platform linux -- Python 3.7.10, pytest-6.0.1, py-1.10.0, pluggy-0.13.1
django: settings: config.settings.settings_pytest (from ini)
rootdir: /app, configfile: setup.cfg
plugins: django-4.3.0, forked-1.0.1, flaky-3.6.0, celery-4.3.0, cov-2.9.0, redis-2.0.0, xdist-1.32.0
collected 10 items

capdb/tests/test_multi.py ..........                                                            [100%]

========================================= 10 passed in 4.28s ==========================================

أتخيل أنه إذا كان بإمكانك تجربة اختبار منعزل من هذا القبيل وتضييق نطاق المشكلة ، فقد يساعد bluetech عندما يعودون إلى العمل على هذا. يمكنك أيضًا إلقاء نظرة فاحصة على التركيبات الخاصة بك ، وربما تعطيلها تمامًا ، ومعرفة ما إذا كان الاختبار المعزول يبدأ في العمل مرة أخرى - لن يكون ذلك مفاجئًا إذا كان هناك حل بديل متعدد الوسائط في تركيباتك يعبث بهذا.

هل هذا يعني أنه إذا كان لدي مشروع متعدد db ، فلا يمكنني استخدام pytest للاختبارات؟

لدي قاعدة بيانات قديمة وأنشأت تطبيقًا مع بعض النماذج التي ترتبط بالجداول في قاعدة البيانات القديمة هذه ، كما أنشأت بعض نقاط النهاية باستخدام Django DRF (مُدار = خطأ) ، لذلك لم يتم إجراء أي عمليات ترحيل.
لذلك ستكون الحالة الأولى في التعليق الأول من قبل bluetech.

تضمين التغريدة

FWIW لقد كافحت بالتأكيد مع عدم وجود نسخة ثابتة

نعم ، أنا متأكد من أننا بحاجة إلى بعض التكامل مع التركيبات هنا.

يجب أن يكون اقتراحك قابلاً للتنفيذ ؛ كل ما نحتاجه حقًا هو معرفة قائمة قواعد البيانات بمجرد تنفيذ الاختبار.

أعتقد أن واجهة برمجة التطبيقات add_django_db ليست رائعة جدًا ، لكن ربما أحتاج إلى التعود عليها قليلاً.

سأفكر في الأمر بالتأكيد. بالطبع إذا كان شخص ما يريد تقديم العلاقات العامة مع اقتراح سيكون ذلك ممكنًا أيضًا.

# لا توجد طريقة لاستخدام pytest.mark هنا؟

حسنًا ، لا توجد حاليًا طريقة لإضافة العلامات مباشرةً إلى المذاهب. هذا هو https://github.com/pytest-dev/pytest/issues/5794. لا يمكنني التفكير في أي طريقة واضحة لدعمه أيضًا.

كانت لدي فكرة أخرى أثناء إجراء التحويل - سيكون من الرائع وجود بعض العلامات التي يمكنني استخدامها لتحذير بشأن قواعد البيانات غير المستخدمة

للحصول على دعم متعدد ديسيبل ، يعتمد pytest-django بشكل كامل تقريبًا على كود django.test ، لذلك من المحتمل أن تمر هذه الميزة عبر Django. قد يكون من الممكن بطريقة ما تتبع ما إذا كان قد تم استخدام اتصال بقاعدة بيانات أثناء الاختبار ، لكنني لست متأكدًا. هناك أيضًا الكثير من الإيجابيات الخاطئة (أو بالأحرى ، الحالات التي تريد فيها الاحتفاظ بقاعدة بيانات على أي حال) ، لذلك ستحتاج بالتأكيد إلى إيقاف التشغيل افتراضيًا.

jgb

جوابي هو إلى حد كبير ما قاله jcushman (شكرًا!) - إنه يعمل هنا ، لذلك سنحتاج إلى مزيد من التفاصيل لمساعدتنا في تضييق نطاق السبب.

تضمين التغريدة

هل هذا يعني أنه إذا كان لدي مشروع متعدد db ، فلا يمكنني استخدام pytest للاختبارات؟

من المفترض أن يكون العكس هو الصحيح - في السابق لم يكن بإمكانك ذلك ، الآن يمكنك ذلك.

إذا قمت بتكوين قاعدة البيانات القديمة في قواعد البيانات الخاصة بك ، فيجب أن تعمل. إذا جربتها ولم تنجح ، فسنحتاج إلى معرفة كيف فشلت.

مرحبًاbluetech
لقد قمت بإنشاء تطبيق قابل لإعادة الاستخدام (دعنا نسميه API) يحتوي على هذه النماذج غير المُدارة.
ثم (في نفس الريبو) "testapp" آخر يقوم بتثبيت واجهة برمجة التطبيقات هذه

في هذا testapp / settings.py لدي قاعدتا بيانات. الأول هو db القديم ، الذي سيتم الاستعلام عنه بواسطة النماذج غير المُدارة في تطبيق API.

DATABASES = {
    'default': {
        'ATOMIC_REQUESTS': True,
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(str(BASE_DIR), 'db.sqlite3'),
         . . . 
    },
    'legacy_db': {
        'ENGINE': django.db.backends.postgresql,
        . . . 
    }
}
DATABASE_ROUTERS = ['my-project.testapp.database_routers.DatabaseRouter']

ثم لدي أيضًا testapp / settings_test.py. في هذا الملف ، أقوم بتعطيل عمليات الترحيل وتحديد قواعد بيانات sqlite وضبط الإدارة = True للنماذج.

from .settings import *
from django.test.runner import DiscoverRunner

class DisableMigrations(object):

    def __contains__(self, item):
        return True
    def __getitem__(self, item):
        return

MIGRATION_MODULES = DisableMigrations()

class UnManagedModelTestRunner(DiscoverRunner):
    """
    Test runner that automatically makes all unmanaged models in your Django
    project managed for the duration of the test run.
    """

    def setup_test_environment(self, *args, **kwargs):
        from django.apps import apps

        self.unmanaged_models = [
            m for m in apps.get_models() if not m._meta.managed
        ]
        for m in self.unmanaged_models:
            m._meta.managed = True
        super(UnManagedModelTestRunner, self).setup_test_environment(
            *args, **kwargs
        )

    def teardown_test_environment(self, *args, **kwargs):
        super(UnManagedModelTestRunner, self).teardown_test_environment(
            *args, **kwargs
        )
        # reset unmanaged models
        for m in self.unmanaged_models:
            m._meta.managed = False


DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.sqlite3",
        "NAME": os.path.join(BASE_DIR, "db_test.sqlite3"),
        "TEST": {
            "ENGINE": "django.db.backends.sqlite3",
            "NAME": os.path.join(BASE_DIR, "db_test.sqlite3"),
        },
    },
    'legacy_db': {
        "ENGINE": "django.db.backends.sqlite3",
        "NAME": os.path.join(BASE_DIR, "db_legacy_test.sqlite3"),
        "TEST": {
            "ENGINE": "django.db.backends.sqlite3",
            "NAME": os.path.join(BASE_DIR, "db_legacy_test.sqlite3"),
        },
    },
}

# Set Django's test runner to the custom class defined above
TEST_RUNNER = "testapp.settings_test.UnManagedModelTestRunner"

إذا أجريت اختبارات وحدة عادية بإعدادات "إنتاج" ، فإنها تفشل كما هو متوقع بسبب عدم وجود علاقات للنماذج غير المُدارة.
./manage.py test --settings=testapp.settings

إذا قمت بالتشغيل باستخدام إعدادات الاختبار ، فستعمل كما هو متوقع.
./manage.py test --settings=testapp.settings_test

ولكن ، إذا ركضت باستخدام pytest.

    def execute(self, query, params=None):
        if params is None:
            return Database.Cursor.execute(self, query)
        query = self.convert_query(query)
>       return Database.Cursor.execute(self, query, params)
E       django.db.utils.OperationalError: no such table: ingredient

إليك خلاصة مع المزيد من الكود (النماذج ، المصنع ، الاختبار ، الإعدادات): https://gist.github.com/gonzaloamadio/14f935d96809299b7f1e9fb88a6e8e94

لقد وضعت نقطة توقف وقمت بفحص DB. وأيضًا كما هو متوقع ، عندما أركض مع مجموعة unittest ، كانت طاولة المكونات موجودة؟

image

ولكن عندما تعمل مع بيتيست .. لا يوجد جدول المكونات هناك

image

تعليق واحد آخر. لقد قمت بتشغيل unittest مع خيار مطول. هذا هو الناتج

❯ ./manage.py test --settings=testapp.settings_test --verbosity=2

Using existing test database for alias 'default' ('db_test.sqlite3')...
Operations to perform:
  Synchronize unmigrated apps: account, admin, core_api, auth, contenttypes, corsheaders, django_extensions, django_filters, messages, rest_framework, runserver_nostatic, sessions, softdelete, staticfiles, test_without_migrations
  Apply all migrations: (none)

Synchronizing apps without migrations:
  Creating tables...
    Creating table auth_permission
    Creating table auth_group
     . . .  more of django core stuff tables
Running migrations:
  No migrations to apply.

Using existing test database for alias 'legacy_db' ('db_legacy_test.sqlite3')...
Operations to perform:
  Synchronize unmigrated apps: account, admin, core_api, auth, contenttypes, corsheaders, django_extensions, django_filters, messages, rest_framework, runserver_nostatic, sessions, softdelete, staticfiles, test_without_migrations
  Apply all migrations: (none)
Synchronizing apps without migrations:
  Creating tables...
    Creating table ingredient                   <--- This is the key.  
    Running deferred SQL...
Running migrations:
  No migrations to apply.

لذلك وجدت هذا الحل : https://stackoverflow.com/questions/30973481/django-test-tables-are-not-being-created/50849037#50849037

افعل في Conftest ما يفعله UnManagedModelTestRunner.

نجح هذا الحل بالنسبة لي bluetech

$ cat conftest.py

import pytest
# Set managed=True for unmanaged models. !! Without this, tests will fail because tables won't be created in test db !!
@pytest.fixture(autouse=True, scope="session")
def __django_test_environment(django_test_environment):
    from django.apps import apps

    get_models = apps.get_models

    for m in [m for m in get_models() if not m._meta.managed]:
        m._meta.managed = True

gonzaloamadio حسنًا ، لا يعتبر pytest TEST_RUNNER (هو نفسه عداء الاختبار) ، لذلك لا تؤثر تعديلاتك عليه. لست متأكدًا مما إذا كنت قادرًا في النهاية على إجراء التعديل في conftest.py أم لا. إذا لم يكن الأمر كذلك ، فيرجى إبلاغي وسأحاول تقديم المساعدة.

لذلك عثرت للتو على هذا القسم من المستندات. نقوم حاليًا بترقية مشروع DB Django متعدد من Django 1.11 إلى Django 3.2 وأيضًا ترقية حزم pytest و pytest-django. لم أكن على علم بكل هذه التغييرات ، لكن بالنسبة لنا ، فقد نجحت في الخروج من الصندوق دون أي مشاكل. الاختبارات تمر دون مشاكل. لذا شكرا لكم على ذلك!

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات