将 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 设置为 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())
````
所以我相信这个问题归结为适当的多数据库支持。 我将关闭这个问题作为重复。
最有用的评论
为那些寻找临时解决方案的人启用 multi_db 的另一个选项。 把它放在 conftest.py 中