Pytest-django: Pengujian menggunakan perlengkapan live_server menghapus data dari migrasi data

Dibuat pada 14 Apr 2016  ·  21Komentar  ·  Sumber: pytest-dev/pytest-django

Saya telah membuat kasus uji sederhana untuk mereproduksi perilaku ini https://github.com/ekiro/case_pytest/blob/master/app/tests.py yang gagal setelah pengujian kedua menggunakan perlengkapan live_server.
Objek MyModel dibuat dalam migrasi, menggunakan RunPython. Sepertinya setelah tes apa pun dengan live_server, setiap baris dari database terpotong. Keduanya, postgresql dan sqlite3 diuji.

EDIT:
Tes

"""
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

Komentar yang paling membantu

Saya baru saja mencapai ini juga dan saya butuh waktu lama untuk melacaknya, meskipun saya cukup familiar dengan kerangka kerja pengujian dan migrasi Django.

Ini sebenarnya adalah pertukaran kinerja yang mengejutkan dari TestCase Django. Ini didokumentasikan di sini:
https://docs.djangoproject.com/en/1.9/topics/testing/overview/#rollback -emulation

Dalam rangkaian pengujian Django biasa, penyelesaiannya terdiri dari menyetel atribut kelas serialized_rollback = True pada kasus pengujian.

Saya tidak tahu bagaimana mencapai efek yang sama dengan kelas tes yang dihasilkan secara dinamis dari pytest-Django.

Semua 21 komentar

Itu karena perlengkapan transactional_db digunakan secara otomatis oleh live_server (tidak perlu menandainya dengan @pytest.mark.django_db ). Ada beberapa masalah / diskusi dalam hal ini, tetapi terakhir kali saya melihat lebih dekat, tidak ada solusi mudah untuk masalah ini yang datang dengan menggunakan migrasi data.
Solusinya adalah dengan menggunakan perlengkapan pytest untuk membungkus migrasi data/perlengkapan data Anda.
(btw: lebih baik masukkan tes ke dalam masalah daripada ke sumber daya eksternal yang mungkin berubah seiring waktu.)

Saya baru saja mencapai ini juga dan saya butuh waktu lama untuk melacaknya, meskipun saya cukup familiar dengan kerangka kerja pengujian dan migrasi Django.

Ini sebenarnya adalah pertukaran kinerja yang mengejutkan dari TestCase Django. Ini didokumentasikan di sini:
https://docs.djangoproject.com/en/1.9/topics/testing/overview/#rollback -emulation

Dalam rangkaian pengujian Django biasa, penyelesaiannya terdiri dari menyetel atribut kelas serialized_rollback = True pada kasus pengujian.

Saya tidak tahu bagaimana mencapai efek yang sama dengan kelas tes yang dihasilkan secara dinamis dari pytest-Django.

Perubahan berikut "menyelesaikan" masalah dengan mengorbankan pemilihan perilaku yang paling tidak efisien tanpa syarat.

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

Teknik berikut berfungsi, tetapi saya tidak dapat merekomendasikannya karena alasan yang agak jelas ...

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)

Apakah opsi untuk mengaktifkan serialized_rollback secara kondisional menjadi solusi yang baik?

Yaitu sesuatu seperti

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

Saya pikir itu bagus.

Dalam kasus penggunaan saya:

  • perlengkapan transactional_db dipicu oleh live_server , tetapi saya tidak keberatan menambahkan perlengkapan django_db untuk menentukan rollback serial
  • tidak berhubungan dengan pytest-Django -- case.serialized_rollback = True masih gagal karena Django mencoba untuk deserialized objek dalam urutan yang tidak menghormati batasan FK, saya pikir itu bug di Django

Oke sempurna :)

Saya tidak punya waktu untuk memperbaikinya sekarang, tetapi menambahkan opsi seperti itu ke penanda django_db seharusnya dan menyetel case.serialized_rollback = True (seperti yang Anda lakukan di atas) seharusnya relatif mudah.

Tiket untuk bug Django yang saya sebutkan di atas: https://code.djangoproject.com/ticket/26552

@pelme -- apakah permintaan tarik ini menerapkan pendekatan "relatif mudah" yang Anda bayangkan?

Terima kasih banyak untuk PR-nya. Implementasinya terlihat benar dan apa yang saya bayangkan. Saya tidak berpikir untuk memiliki perlengkapan serialized_rollback tetapi memang berguna untuk dapat memaksa perilaku ini dari perlengkapan lain.

Kami akan membutuhkan tes untuk ini juga, tetapi implementasinya terlihat benar!

Saya akan mencoba mencari waktu untuk memoles tambalan (saya bahkan belum menjalankannya). Saya perlu memperbaiki bug yang disebutkan di Django sebelum saya dapat menggunakan ini.

Versi peretasan yang diperbarui di atas untuk 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)

Terima kasih atas pembaruannya dan maaf saya tidak bisa memasukkan ini ke versi 3.0. Saya akan mencoba untuk mendapatkan ini untuk rilis berikutnya!

Apakah ada kesempatan untuk melihatnya di rilis berikutnya?

Terima kasih

@Wierrat
Ya mungkin.

Bisakah membantu kami dengan tes untuk https://github.com/pytest-dev/pytest-Django/pull/353 sebelum itu?

Hai teman-teman,
ini status terbarunya apa? Secara khusus saya menggunakan pytest sebagai pelari pengujian saya dengan kelas StaticLiveServerTestCase dari Django. Saya telah menetapkan atribut kelas serialized_rollback = True tetapi itu tampaknya hanya berlaku ketika menjalankan tes pertama dari urutan.

Baru saja tertangkap oleh yang satu ini. Melihat-lihat tampaknya ada beberapa PR tentang masalah ini tetapi tidak satupun dari mereka tampaknya telah digabungkan.
Apakah ada alasan khusus mengapa masalah ini tetap terbuka?

beberapa PR

Kemungkinan/hanya https://github.com/pytest-dev/pytest-Django/issues/329 , bukan?

Terakhir kali saya bertanya tentang bantuan dengan tes, dan itu memiliki beberapa konflik (mungkin hanya karena hitam).

Saya masih menyarankan siapa pun yang terpengaruh untuk membantu itu - saya tidak menggunakannya sendiri.

Terima kasih atas balasan cepatnya @blueyed.
Pengetahuan saya tentang pytest/pytest-Django minimal tetapi saya akan memasukkannya ke daftar suatu hari nanti/mungkin! :D

Versi terbaru dari peretasan di atas untuk 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')

Saya harus bergantung pada perlengkapan transactional_db alih-alih menyebutnya sebagai pytest tidak lagi memungkinkan memanggil fungsi perlengkapan secara langsung. Saya kemudian mendapatkan def perlengkapan dari perlengkapan asli dan menambahkan finalizer ke dalamnya. Saat memasukkan pada indeks 1 masih berfungsi, saya memasukkan sebelum semua finalis dan menggunakan django_db_blocker dalam fungsi pemulihan karena tampaknya sedikit kurang rapuh.

Sunting: akhirnya dihapus coba yang tidak perlu.

@lukaszb

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

Saya menggunakan versi pytest-Django ini (https://github.com/pytest-dev/pytest-Django/pull/721/files),
Tapi tetap saja kesalahan.

image

Apakah halaman ini membantu?
0 / 5 - 0 peringkat