Pytest-django: データ移行からデータを削除するlive_serverフィクスチャを使用したテスト

作成日 2016年04月14日  ·  21コメント  ·  ソース: pytest-dev/pytest-django

この動作を再現するための簡単なテストケースを作成しましたhttps://github.com/ekiro/case_pytest/blob/master/app/tests.pyこれはlive_serverフィクスチャを使用した2回目のテスト後に失敗します。
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とは無関係-DjangoがFK制約を尊重しない順序でオブジェクトを逆シリアル化しようとするため、 case.serialized_rollback = Trueまだ失敗します。これは、Djangoのバグだと思います。

大丈夫完璧 :)

私は今、修正の作業に時間がありませんが、このようなオプションを追加するdjango_dbマーカーは、および設定すべきであるcase.serialized_rollback = True (あなたが上記のやったように)比較的簡単でなければなりません。

上記のDjangoバグのチケット: https

@ pelme-このプルリクエストは、あなたが想像している「比較的簡単な」アプローチを実装していますか?

PRありがとうございます。 実装は正しく、私が想像したものに見えます。 serialized_rollbackフィクスチャがあるとは思いませんでしたが、他のフィクスチャからこの動作を強制できると確かに便利です。

これについてもテストが必要ですが、実装は正しいようです。

パッチを磨く時間を見つけようとします(まだ実行していません)。 これを使用する前に、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に組み込むことができませんでした。 私は次のリリースのためにこれに到達しようとします!

次のリリースでそれを見る機会はありますか?

ありがとう

@Wierrat
はい、たぶん。

その前にhttps://github.com/pytest-dev/pytest-django/pull/353のテストを手伝ってもらえますか?

こんにちは皆さん、
これの最新のステータスは何ですか? 特に、DjangoのStaticLiveServerTestCaseクラスのテストランナーとしてpytestを使用しています。 クラス属性serialized_rollback = Trueを設定しましたが、これはシーケンスから最初のテストを実行する場合にのみ有効であるようです。

これに引っかかった。 周りを見回すと、この問題についていくつかのPRがあったようですが、それらのいずれもマージされていないようです。
この問題が未解決のままである特別な理由はありますか?

いくつかのPR

可能性が高い/のみhttps://github.com/pytest-dev/pytest-django/issues/329 、いいえ?

前回、テストのヘルプについて質問しましたが、いくつかの競合があります(おそらく黒が原因です)。

私はまだ影響を受けた人にそれを手伝うことを提案します-私はそれを自分で使用していません。

クイック返信@blueyedをありがとう。
pytest / pytest-djangoに関する私の知識は最小限ですが、それを私のsomday /多分リストに載せます! :NS

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

pytestではフィクスチャ関数を直接呼び出すことができなくなったため、元のtransactional_dbフィクスチャを呼び出すのではなく、それに依存する必要がありました。 次に、元のフィクスチャのフィクスチャdefを取得し、それにファイナライザを追加します。 インデックス1での挿入は引き続き機能しますが、すべてのファイナライザーの前に挿入し、復元関数でdjango_db_blockerを使用します。これは、少し壊れにくいように見えるためです。

編集:最後に不要な試行を削除しました。

@lukaszb

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 評価