Pytest-django: الاختبارات باستخدام live_server fixture لإزالة البيانات من عمليات ترحيل البيانات

تم إنشاؤها على ١٤ أبريل ٢٠١٦  ·  21تعليقات  ·  مصدر: pytest-dev/pytest-django

لقد قمت بإنشاء حالة اختبار بسيطة لإعادة إنتاج هذا السلوك https://github.com/ekiro/case_pytest/blob/master/app/tests.py والذي فشل بعد الاختبار الثاني باستخدام Live_server fixture.
يتم إنشاء كائنات MyModel أثناء الترحيل باستخدام RunPython. يبدو أنه بعد أي اختبار باستخدام live_server ، يتم اقتطاع كل صف من قاعدة البيانات. تم اختبار كل من postgresql و sqlite3.

تعديل:
الاختبارات

"""
MyModel objects are created in migration
Test results:
    app/tests.py::test_no_live_server PASSED
    app/tests.py::test_live_server PASSED
    app/tests.py::test_live_server2 FAILED
    app/tests.py::test_no_live_server_after_live_server FAILED
"""

import pytest

from .models import MyModel


@pytest.mark.django_db()
def test_no_live_server():
    """Passed"""
    assert MyModel.objects.count() == 10


@pytest.mark.django_db()
def test_live_server(live_server):
    """Passed"""
    assert MyModel.objects.count() == 10


@pytest.mark.django_db()
def test_live_server2(live_server):
    """Failed, because count() returns 0"""
    assert MyModel.objects.count() == 10


@pytest.mark.django_db()
def test_no_live_server_after_live_server():
    """Failed, because count() returns 0"""
    assert MyModel.objects.count() == 10

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

لقد قمت للتو بذلك أيضًا واستغرق الأمر وقتًا طويلاً لتعقبه ، على الرغم من أنني على دراية معقولة بإطار عمل اختبار Django والهجرات.

هذه في الواقع مقايضة أداء مفاجئة لـ Django TestCase . تم توثيقه هنا:
https://docs.djangoproject.com/en/1.9/topics/testing/overview/#rollback -emulation

في مجموعة اختبار Django العادية ، يتمثل الحل البديل في تعيين سمة فئة serialized_rollback = True في حالة الاختبار.

لا أعرف كيف أحقق نفس التأثير مع فصول الاختبار التي تم إنشاؤها ديناميكيًا في pytest-django.

ال 21 كومينتر

هذا بسبب استخدام أداة transactional_db تلقائيًا بواسطة live_server (ليست هناك حاجة لتمييزها بـ @pytest.mark.django_db ). هناك العديد من القضايا / المناقشات في هذا الصدد ، ولكن في المرة الأخيرة التي نظرت فيها عن كثب ، لم يكن هناك حل سهل لهذه المشكلة التي تأتي مع استخدام عمليات ترحيل البيانات.
الحل البديل هو استخدام تركيبات pytest لالتفاف عمليات ترحيل البيانات / تركيبات البيانات.
(راجع للشغل: من الأفضل وضع الاختبارات مضمنة في المشكلة بدلاً من وضعها في مورد خارجي قد يتغير بمرور الوقت.)

لقد قمت للتو بذلك أيضًا واستغرق الأمر وقتًا طويلاً لتعقبه ، على الرغم من أنني على دراية معقولة بإطار عمل اختبار Django والهجرات.

هذه في الواقع مقايضة أداء مفاجئة لـ Django TestCase . تم توثيقه هنا:
https://docs.djangoproject.com/en/1.9/topics/testing/overview/#rollback -emulation

في مجموعة اختبار Django العادية ، يتمثل الحل البديل في تعيين سمة فئة serialized_rollback = True في حالة الاختبار.

لا أعرف كيف أحقق نفس التأثير مع فصول الاختبار التي تم إنشاؤها ديناميكيًا في pytest-django.

التغيير التالي "يحل" المشكلة على حساب الاختيار غير المشروط للسلوك الأقل كفاءة.

--- pytest_django/fixtures.py.orig  2016-04-27 17:12:25.000000000 +0200
+++ pytest_django/fixtures.py   2016-04-27 17:21:50.000000000 +0200
@@ -103,6 +103,7 @@

     if django_case:
         case = django_case(methodName='__init__')
+        case.serialized_rollback = True
         case._pre_setup()
         request.addfinalizer(case._post_teardown)

تعمل التقنية التالية ، لكن لا يمكنني أن أوصي بها لأسباب واضحة إلى حد ما ...

import pytest
from django.core.management import call_command
from pytest_django.fixtures import transactional_db as _transactional_db


def _reload_fixture_data():
    fixture_names = [
        # Create fixtures for the data created by data migrations
        # and list them here.
    ]
    call_command('loaddata', *fixture_names)


@pytest.fixture(scope='function')
def transactional_db(request, _django_db_setup, _django_cursor_wrapper):
    """
    Override a pytest-django fixture to restore the contents of the database.

    This works around https://github.com/pytest-dev/pytest-django/issues/329 by
    restoring data created by data migrations. We know what data matters and we
    maintain it in (Django) fixtures. We don't read it from the database. This
    causes some repetition but keeps this (pytest) fixture (almost) simple.

    """
    try:
        return _transactional_db(request, _django_db_setup, _django_cursor_wrapper)
    finally:
        # /!\ Epically shameful hack /!\ _transactional_db adds two finalizers:
        # _django_cursor_wrapper.disable() and case._post_teardown(). Note that
        # finalizers run in the opposite order of that in which they are added.
        # We want to run after case._post_teardown() which flushes the database
        # but before _django_cursor_wrapper.disable() which prevents further
        # database queries. Hence, open heart surgery in pytest internals...
        finalizers = request._fixturedef._finalizer
        assert len(finalizers) == 2
        assert finalizers[0].__qualname__ == 'CursorManager.disable'
        assert finalizers[1].__qualname__ == 'TransactionTestCase._post_teardown'
        finalizers.insert(1, _reload_fixture_data)

هل خيار التمكين المشروط serialized_rollback سيكون حلاً جيدًا؟

أي شيء من هذا القبيل

@pytest.mark.django_db(transaction=True, serialized_rollback=True)
def test_foo():
    ...

أعتقد أن ذلك سيكون جيدًا.

في حالة الاستخدام الخاصة بي:

  • يتم تشغيل تركيبات transactional_db بواسطة live_server ، لكنني لا أمانع في إضافة تركيبات django_db لتحديد التراجع المتسلسل
  • لا علاقة له بـ pytest-django - case.serialized_rollback = True لا يزال يفشل لأن Django يحاول إلغاء تسلسل الكائنات بترتيب لا يحترم قيود FK ، أعتقد أن هذا خطأ في Django

حسنا ممتاز :)

ليس لدي وقت للعمل على إصلاح في الوقت الحالي ، ولكن إضافة مثل هذا الخيار إلى علامة django_db يجب أن يكون وإعداد case.serialized_rollback = True (كما فعلت أعلاه) أمرًا سهلاً نسبيًا.

تذكرة خطأ Django التي ذكرتها أعلاه: https://code.djangoproject.com/ticket/26552

pelme - هل يطبق طلب السحب هذا النهج "المباشر نسبيًا" الذي تتخيله؟

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

سنحتاج إلى اختبار لهذا أيضًا ، لكن التنفيذ يبدو صحيحًا!

سأحاول إيجاد الوقت لتلميع التصحيح (لم أقم بتشغيله بعد). أحتاج إلى إصلاح الخطأ المذكور أعلاه في Django أيضًا قبل أن أتمكن من استخدام هذا.

نسخة محدثة من الاختراق أعلاه لـ pytest-django ≥ 3.0:

@pytest.fixture(scope='function')
def transactional_db(request, django_db_setup, django_db_blocker):
    """
    Override a pytest-django fixture to restore the contents of the database.

    This works around https://github.com/pytest-dev/pytest-django/issues/329 by
    restoring data created by data migrations. We know what data matters and we
    maintain it in (Django) fixtures. We don't read it from the database. This
    causes some repetition but keeps this (pytest) fixture (almost) simple.

    """
    try:
        return _transactional_db(request, django_db_setup, django_db_blocker)
    finally:
        # /!\ Epically shameful hack /!\ _transactional_db adds two finalizers:
        # django_db_blocker.restore() and test_case._post_teardown(). Note that
        # finalizers run in the opposite order of that in which they are added.
        # We want to run after test_case._post_teardown() flushes the database
        # but before django_db_blocker.restore() prevents further database
        # queries. Hence, open heart surgery in pytest internals...
        finalizers = request._fixturedef._finalizer
        assert len(finalizers) == 2
        assert finalizers[0].__qualname__ == '_DatabaseBlocker.restore'
        assert finalizers[1].__qualname__ == 'TransactionTestCase._post_teardown'
        finalizers.insert(1, _reload_fixture_data)

شكرًا على التحديث وآسف لأنني لم أتمكن من الوصول إلى هذا الإصدار 3.0. سأحاول الوصول إلى هذا للإصدار القادم!

هل هناك أي فرصة لرؤيتها في الإصدار القادم؟

شكرا

تضمين التغريدة
نعم ، على الأرجح.

هل يمكن أن تساعدنا في اختبار https://github.com/pytest-dev/pytest-django/pull/353 قبل ذلك ثم من فضلك؟

مرحبا يا رفاق،
ما هي آخر حالة لهذا؟ على وجه الخصوص ، أنا أستخدم pytest كعداء اختباري مع فئة StaticLiveServerTestCase من Django. لقد قمت بتعيين سمة الفئة serialized_rollback = True ولكن يبدو أن ذلك ساري المفعول فقط عند تنفيذ الاختبار الأول من التسلسل.

تم القبض للتو من قبل هذا واحد. بالنظر حولك ، يبدو أنه كان هناك بعض العلاقات العامة حول هذه المشكلة ولكن لا يبدو أن أيًا منها قد تم دمجه.
هل هناك سبب معين لتبقى هذه القضية مفتوحة؟

بعض العلاقات العامة

من المحتمل / فقط https://github.com/pytest-dev/pytest-django/issues/329 ، أليس كذلك؟

في المرة الأخيرة التي سألت فيها عن المساعدة في الاختبار ، كان بها العديد من التعارضات (ربما بسبب اللون الأسود فقط).

ما زلت أقترح على أي شخص متضرر المساعدة في ذلك - أنا لا أستخدمه بنفسي.

شكرا على الرد السريعblueyed.
معرفتي بـ pytest / pytest-django ضئيلة لكنني سأضع ذلك في قائمة يوم ما / ربما! :د

نسخة محدثة من الاختراق أعلاه لـ pytest 5.2.1 ، pytest-django 3.5.1:

from functools import partial

@pytest.fixture
def transactional_db(request, transactional_db, django_db_blocker):
    # Restore DB content after all of transactional_db's finalizers have
    # run. Finalizers are run in the opposite order of that in which they
    # are added, so we prepend the restore to the front of the list.
    #
    # Works for pytest 5.2.1, pytest-django 3.5.1
    restore = partial(_restore_db_content, django_db_blocker)
    finalizers = request._fixture_defs['transactional_db']._finalizers
    finalizers.insert(0, restore)

    # Simply restoring after yielding transactional_db wouldn't work because
    # it would run before transactional_db's finalizers which contains the truncate.
    return transactional_db

def _restore_db_content(django_db_fixture, django_db_blocker):
    with django_db_blocker.unblock():
        call_command('loaddata', '--verbosity', '0', 'TODO your fixture')

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

تحرير: إزالة غير ضرورية حاول في النهاية.

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

https://github.com/pytest-dev/pytest-django/issues/848

أستخدم هذا الإصدار من pytest-django (https://github.com/pytest-dev/pytest-django/pull/721/files) ،
لكن لا يزال خطأ.

image

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