pytest-django не выполняет очистку между тестами при использовании Django с несколькими базами данных. Проблема связана с этим вопросом StackOverflow: http://stackoverflow.com/questions/10121485/django-testcase-not-using-transactions-on-secondary-database
Приспособление db
незаметно использует TestCase Django.
Вот код из оригинального прибора 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',
},
}
Большинство / все мои модели связаны со вторыми «операциями» базы данных. Я считаю, что TestCase Django будет правильно сбрасывать транзакции в базе данных по умолчанию, но не в других, кроме 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 =====================================
Вот результат использования собственного прибора db
pytest_django:
$ 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
, но нам также нужны какие-то тесты для этого, поэтому нам также нужна настройка вторичной базы данных. Если кто-то захочет над этим поработать, было бы здорово :)
Я использовал обходные пути, упомянутые в первом сообщении
В Django было внесено изменение, чтобы заставить код, обрабатывающий setup / tearDown, искать атрибут multi_db
class вместо атрибута экземпляра.
Для меня сработало изменение
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. ком / 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 не могли бы вы поделиться полным
По какой-то причине pytest_sessionstart()
в нашей базе conftest.py
ничего не делал. Взял страницу из # 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()
_Редактировать: мы перешли к фиксации сеанса.
Чтобы не потерять суперсилы pytest, переключившись на тестовые примеры unittest Django, я скопировал и пропатчил внутренности 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
fixture или pytest.mark.django_db
mark.
@jcushman не могли бы вы поделиться полным
Добавление ответа на случай, если кто-то еще столкнется с этим.
Вам нужно добавить приведенный ниже код в conftest.py
. Этот прибор подбирается автоматически, поэтому нет необходимости добавлять его в тест.
`` ру
def pytest_sessionstart (сеанс):
из django.test импорт TransactionTestCase
TransactionTestCase.databases = set(settings.DATABASES.keys())
`` ''
Поэтому я считаю, что эта проблема сводится к правильной поддержке нескольких баз данных. Я закрою этот выпуск как дубликат.
Самый полезный комментарий
Еще один вариант включения multi_db для тех, кто ищет временное решение. Поместите это в conftest.py