pytest-django não limpa entre os testes ao usar Django com múltiplos bancos de dados. O problema está relacionado a esta questão StackOverflow: http://stackoverflow.com/questions/10121485/django-testcase-not-using-transactions-on-secondary-database
O fixture db
usa o TestCase do Django em segundo plano.
Aqui está o código do fixture db
:
case = TestCase(methodName='__init__')
case._pre_setup()
request.addfinalizer(case._post_teardown)
request.addfinalizer(_django_cursor_wrapper.disable)
Aqui está como eu corrigi-lo como uma solução alternativa em meu código:
case = TestCase(methodName='__init__')
case.multi_db = True
case._pre_setup()
request.addfinalizer(case._post_teardown)
request.addfinalizer(_django_cursor_wrapper.disable)
Obviamente, esse sinalizador multi_db existe e o padrão é False por um motivo. Não tenho certeza da maneira certa de incorporar o suporte a multi_db em pytest_django, e não tenho certeza da maneira certa de testar tal mudança.
Se você tiver uma sugestão, posso trabalhar em uma solicitação pull.
Você poderia dar um exemplo de teste usando sua versão corrigida que usa dois bancos de dados? Eu acho que ajudaria a entender como vários bancos de dados são usados e o que você precisa de controle sobre a configuração de multi_db como true.
DATABASES = {
'default': {
'ENGINE' : 'django.db.backends.sqlite3',
'NAME' : 'default.db',
},
'operations': {
'ENGINE' : 'django.db.backends.sqlite3',
'NAME' : 'operations.db',
},
}
A maioria / todos os meus modelos estão associados às "operações" do segundo banco de dados. Eu acredito que o TestCase do Django irá liberar transações corretamente no banco de dados padrão, mas não em outros, a menos que multi_db=True
.
Aqui estão alguns exemplos de testes:
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
Aqui está a saída com meu fixture db
corrigido, descrito acima:
$ 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 =====================================
Aqui está a saída com o próprio db
fixture de 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 ===============================
Então, olhando rapidamente, parece que um acessório multi_db
e pytest.mark.django_db(multi=True)
podem funcionar como uma API? Alguém mais tem ideias melhores?
Isso parece razoável. Certamente não tenho outras ideias, melhores ou não.
Olá. Existe alguma discussão sobre o suporte de multi_db
além deste problema? Os documentos dizem que é preciso entrar em contato, mas qual é o status atual disso?
@slafs Não houve muitas discussões / solicitações para suporte a vários bancos de dados além disso.
Seria muito simples passar multi_db=True
, mas também precisamos de algum tipo de teste para isso, portanto, também precisamos de um banco de dados secundário configurado. Se alguém estiver disposto a trabalhar nisso, seria ótimo :)
Eu tenho usado a solução alternativa que @ftobia menciona no post de abertura, mas depois de atualizar para o Django 1.8.4 do 1.7 ele parou de funcionar.
Houve uma mudança no Django para fazer o manuseio do código setup / tearDown olhar para um atributo de classe multi_db
invés de um atributo de instância.
Alterar a solução alternativa de @ftobia para o seguinte funcionou para mim:
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:
Isso está nos impedindo de mudar do executor de teste do Django para o pytest.
Por que não usar django_case.multi_db = True
aqui https://github.com/pytest-dev/pytest-django/blob/master/pytest_django/fixtures.py#L107 por padrão?
+1
+1
Acho que ter um marcador no marcador django_db como faria com uma boa API:
@pytest.mark.django_db(transactional=True, multi_db=True)
def test_a():
pass
A implementação deve ser relativamente semelhante a reset_sequences
(PR # 308) ou serialized_rollback
(PR # 353).
A parte "complicada" seria melhorar o conjunto de testes internos do pytest-django para conter vários bancos de dados.
Se alguém quiser trabalhar nisso, com certeza irei revisá-lo e ajudar na fusão. Sinta-se à vontade para começar a trabalhar em uma RP se for algo do seu interesse!
Deixou alguns comentários em https://github.com/pytest-dev/pytest-django/pull/397#issuecomment -261987751, e criou um PR, que permitiria alterar isso de forma genérica: https: // github. com / pytest-dev / pytest-django / pull / 431.
Outra opção para habilitar o multi_db para quem procura uma solução temporária. Coloque isso em conftest.py
def pytest_sessionstart(session):
from django.test import TransactionTestCase
TransactionTestCase.multi_db = True
Para qualquer pessoa em busca de soluções alternativas, observe que multi_db
foi descontinuado no Django 2.2. A substituição que funcionou para mim é:
TransactionTestCase.databases = set(settings.DATABASES.keys())
@jcushman , você se importa em compartilhar o dispositivo completo ou o snippet de código que usou?
Por alguma razão, pytest_sessionstart()
em nossa base conftest.py
não estava fazendo nada. Pegou uma página de # 342 junto com pytest padrão monkeypatch
stuff e pousou no seguinte para nosso uso de banco de dados múltiplo:
# 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()
_Editar: mudamos para um fixture de sessão._
A fim de não perder os superpoderes do pytest mudando para os casos de teste unittest do Django, copiei e apliquei o patch interno do pytest-django a este hack feio (ainda funcionando!):
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
Com ele, pode-se usar algo como:
class TestCase(object):
def test_ok(
self,
db_case,
...
):
db_case(dbs=('non_default_db_alias',))
Deve ser usado em vez de db
fixture ou pytest.mark.django_db
mark.
@jcushman , você se importa em compartilhar o dispositivo completo ou o snippet de código que usou?
Adicionando resposta no caso de alguém topar com isso.
Você precisa adicionar o código abaixo em conftest.py
. Este acessório é selecionado automaticamente, então não há necessidade de adicioná-lo a um teste.
`` `py
def pytest_sessionstart (sessão):
de django.test import TransactionTestCase
TransactionTestCase.databases = set(settings.DATABASES.keys())
`` ``
Então eu acredito que esse problema se resume ao suporte multi-db adequado. Encerrarei este problema como uma duplicata daquele.
Comentários muito úteis
Outra opção para habilitar o multi_db para quem procura uma solução temporária. Coloque isso em conftest.py