Pytest-django: 支持多个数据库测试之间的清理

创建于 2014-03-20  ·  20评论  ·  资料来源: pytest-dev/pytest-django

将 Django 与多个数据库一起使用时,pytest-django 不会在测试之间进行清理。 该问题与此 StackOverflow 问题有关: http ://stackoverflow.com/questions/10121485/django-testcase-not-using-transactions-on-secondary-database

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条评论

您能否使用使用两个数据库的修补版本进行示例测试? 我认为这将有助于了解如何使用多个数据库以及您需要控制什么,然后将 multi_db 设置为 true。

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

我的大多数/所有模型都与第二个数据库“操作”相关联。 我相信 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 后,它停止工作。

Django 进行了更改,以使代码处理 setup/tearDown 查找multi_db类属性而不是实例属性。

@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在 Django 2.2 中已被弃用。 对我有用的替代品是:

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

@jcushman你介意分享你使用的完整装置或代码片段吗?

无论出于何种原因,我们基地conftest.py中的pytest_sessionstart()没有做任何事情。 从#342 中取出一个页面以及标准的 pytest monkeypatch内容,并为我们的多数据库使用提供了以下内容:

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

_编辑:我们转移到了一个会话装置。_

为了不通过切换到 unittest Django 测试用例来失去 pytest 的超能力,我将 pytest-django 内部结构复制并修补到这个丑陋的(但有效!)hack:

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 导入 TransactionTestCase

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

````

所以我相信这个问题归结为适当的多数据库支持。 我将关闭这个问题作为重复。

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

tolomea picture tolomea  ·  6评论

asfaltboy picture asfaltboy  ·  5评论

ojake picture ojake  ·  6评论

aljosa picture aljosa  ·  8评论

tolomea picture tolomea  ·  4评论