Pytest-django: 複数のデータベースを使用したテスト間のクリーンアップをサポート

作成日 2014年03月20日  ·  20コメント  ·  ソース: pytest-dev/pytest-django

pytest-djangoは、複数のデータベースでDjangoを使用している場合、テスト間でクリーンアップされません。 問題はこのStackOverflowの質問に関連しています: http

dbフィクスチャは、内部でDjangoのTestCaseを使用します。

元のdbフィクスチャのコードは次のとおりです。

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

コードの回避策としてパッチを適用した方法は次のとおりです。

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

明らかに、そのmulti_dbフラグが存在し、理由によりデフォルトでFalseになっています。 multi_dbサポートをpytest_djangoに組み込む正しい方法がわかりません。また、そのような変更をテストする正しい方法もわかりません。

提案があれば、プルリクエストに対応できます。

最も参考になるコメント

一時的な解決策を探している人のためにmulti_dbを有効にする別のオプション。 これをconftest.pyに入れてください

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

全てのコメント20件

2つのデータベースを使用するパッチを適用したバージョンを使用したテストの例を教えてください。 これは、複数のデータベースがどのように使用されているか、およびmulti_dbをtrueに設定してから他のデータベースを制御する必要があるかを理解するのに役立つと思います。

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

ほとんど/すべてのモデルは、2番目のデータベース「操作」に関連付けられています。 DjangoのTestCaseは、デフォルトのデータベースでトランザクションを正しくフラッシュすると思いますが、 multi_db=Trueない限り、他のデータベースではフラッシュしません。

ここにいくつかのサンプルテストがあります:

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

上記のパッチを適用したdbフィクスチャの出力は次のとおりです。

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

pytest_django独自のdbフィクスチャを使用した出力は次のとおりです。

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

簡単に見ると、 multi_dbフィクスチャとpytest.mark.django_db(multi=True)がAPIとして機能するように見えますか? 他の誰かがより良いアイデアを持っていますか?

それは合理的に聞こえます。 私は確かに他のアイデアを持っていません。

こんにちは。 この問題以外にmulti_dbサポートについての議論はありますか? ドキュメントには連絡を取るべきだと書かれていますが、これに関する現在の状況はどうですか?

@slafsこれ以外に、マルチデータベースのサポートに関する議論や要求はあまりありませんでした。

multi_db=Trueを渡すのは非常に簡単ですが、これには何らかのテストも必要なので、セカンダリデータベースを設定する必要もあります。 誰かがこれに取り組む気があるなら、それは素晴らしいでしょう:)

私は冒頭の投稿で回避策の@ftobiaの言及を使用してきましたが、1.7からDjango 1.8.4にアップグレードした後、動作を停止しました。

コード処理setup / tearDownがインスタンス属性ではなくmulti_dbクラス属性を検索するようにDjangoが変更されました。

@ftobiaの回避策を次のように変更すると、

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:

これにより、Djangoテストランナーからpytestに切り替えることができなくなります。

ここでdjango_case.multi_db = True使用しないのはなぜですかhttps://github.com/pytest-dev/pytest-django/blob/master/pytest_django/fixtures.py#L107デフォルトで?

+1

+1

django_dbマーカーにマーカーを付けると、APIがうまくいくと思います。

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

実装は、 reset_sequences (PR#308)またはserialized_rollback (PR#353)に比較的類似している必要があります。

「トリッキー」な部分は、pytest-djangoの内部テストスイートを改善して、複数のデータベースを含めることです。

誰かがこれで働きたいのなら、私は確かにそれを見直して、それをマージするのを手伝います。 これがあなたにとって興味深いものであるならば、PRに取り掛かることを遠慮なく始めてください!

https://github.com/pytest-dev/pytest-django/pull/397#issuecomment -261987751にコメントを残し、PRを作成しました。これにより、一般的な方法でこれを変更できます:https:// github。 com / pytest-dev / pytest-django / pull / 431。

一時的な解決策を探している人のためにmulti_dbを有効にする別のオプション。 これをconftest.pyに入れてください

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

回避策を探している他の人のために、 multi_dbはDjango2.2で非推奨になっていることに注意してください。 私のために働いた代替品は次のとおりです。

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

@jcushman使用した完全なフィクスチャまたはコードスニペットを共有してもよろしいですか?

何らかの理由で、ベースconftest.py pytest_sessionstart()は何もしていませんでした。 標準のpytest monkeypatchものと一緒に#342からページを取得し、マルチデータベースで使用するために次のものに到達しました。

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

_編集:セッションフィクスチャに移動しました。_

単体テストのDjangoテストケースに切り替えてpytestの超大国を失わないようにするために、pytest-djangoの内部をこの醜い(まだ機能している!)ハックにコピーしてパッチを適用しました。

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

これを使用すると、次のようなものを使用できます。

class TestCase(object):

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

これは dbフィクスチャまたはpytest.mark.django_dbマークの代わりに使用

@jcushman使用した完全なフィクスチャまたはコードスニペットを共有してもよろしいですか?

他の誰かがこれにぶつかる場合に備えて返信を追加します。

以下のコードをconftest.pyに追加する必要があります。 このフィクスチャは自動的に取得されるため、テストに追加する必要はありません。

`` `py
def pytest_sessionstart(session):
django.testからimportTransactionTestCase

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

`` ``

したがって、この問題は適切なマルチデータベースのサポートに起因すると思います。 その複製としてこの問題を閉じます。

このページは役に立ちましたか?
0 / 5 - 0 評価