Eu tenho uma situação em que preciso carregar um acessório de banco de dados enorme que é criado por uma função. O acessório é necessário em todos os testes de API. Então eu fiz uma sessão de fixação em conftest.py
que faria isso. Mas o problema é que pytest
lança a seguinte exceção, mesmo que eu tenha marcado django_db
:
E Failed: Database access not allowed, use the "django_db" mark to enable it.
Abaixo está o meu trecho de código.
from permission.helpers import update_permissions
pytestmark = [
pytest.mark.django_db(transaction = True),]
@pytest.fixture(scope="session", autouse = True)
def permission(request):
load_time_consuming_db_fixture()
@ludbek também perdemos esse recurso e criamos um plugin para este recurso:
https://github.com/tipsi/pytest-tipsi-django (uso: https://github.com/tipsi/pytest-tipsi-django/blob/master/test_django_plugin/app/tests/test_transactions.py)
Em conjunção com:
https://github.com/tipsi/pytest-tipsi-testing
Ele oferece a capacidade de aninhar transações e corrigir a ordem de execução/encerramento.
@cybergrind Obrigado por responder. Com certeza vou conferir e te conto como foi.
Este tem sido o maior ponto problemático para mim vindo dos testes baseados em classe UnitTest do Django, para pytest-django -- no Django usamos setUpTestData
para executar operações de banco de dados caras uma vez (equivalente a fixtures pytest com escopo de sessão), e então há um truque astuto para executar obj.refresh_from_db()
no setUp
para atualizar as referências de classe.
Mesmo se eu estiver apenas criando uma instância de modelo de banco de dados e recarregando-a a cada TC, isso é quase sempre mais rápido do que criar em cada caso de teste.
Seria ótimo se pudéssemos obter os fixtures com escopo de sessão de pytest-tipsi-django mesclados upstream, se isso for uma possibilidade; Demorou um pouco de escavação para encontrar este problema e solução.
oi @paultiplady
Não tenho certeza se essa abordagem de pytest-tipsi-djanjo
se encaixa no modelo de teste usual para pytest
. A diferença mais notável é o acabamento dos equipamentos: atualmente pytest
não finaliza explicitamente os equipamentos desnecessários com um escopo mais amplo. Portanto, você precisa terminar as transações explicitamente em uma ordem específica e, em geral, isso pode causar efeitos muito diferentes (atualmente pytest
pode manter os fixtures ativos mesmo se o teste ativo e seus fixtures não exigirem nada).
Tivemos que alterar os testes em nosso projeto para essa abordagem porque às vezes precisamos testar alguns grandes cenários e substituímos o gerenciamento manual de transações existente em testes enormes por acessórios um pouco melhores, mas ainda requer atenção nos testes de pedidos.
No momento, vejo apenas uma solução para isso: colocar algum tipo de FAQ na documentação.
Obrigado pelo detalhe adicional @cybergrind. Eu me aprofundei um pouco mais, mas não tenho tempo para hoje -- aqui é onde eu tenho que fazer, eu apreciaria uma verificação de sanidade sobre se essa abordagem é útil ou não, já que estou não tão familiarizado com os internos do pytest.
Também não entendo o que você quer dizer com "pytest não termina explicitamente acessórios desnecessários com um escopo mais amplo", você poderia expandir um pouco mais, por favor? Isso se refere aos finalizadores? Isso pode afetar o que escrevi abaixo.
O plugin pytest-django usa a marca django_db
, que é tratada em _django_db_marker em plugin.py (https://github.com/pytest-dev/pytest-django/blob/master/pytest_django/plugin.py #L375), chamando o fixture db
com escopo de função (https://github.com/pytest-dev/pytest-django/blob/master/pytest_django/fixtures.py#L142). Este acessório instancia um Django TestCase e chama sua função _pre_setup
(e enfileira o _post_teardown
).
Posso ver algumas opções:
Gostaria de saber se poderíamos estender _django_db_marker
para opcionalmente fazer aulas ou sessões
configuração com escopo também, que faria essencialmente a mesma coisa que db
, mas chamando o equivalente a cls.setUpTestData
ou uma função passada na marca kwargs.
Para escopo de classe, e estou assumindo também para escopo de sessão, o comportamento esperado seria reverter o banco de dados posteriormente, então basicamente precisamos de acessórios com escopo que acionem na ordem correta e que cada um configure seu próprio atomic transação. Eu acredito que isso significa que isso precisa ser uma modificação no fixture db
, ao invés de um fixture separado que corre ao lado dele.
Dessa forma, acionaríamos corretamente qualquer configuração de nível de classe/sessão especificada, e essa configuração seria chamada uma vez por classe/sessão. Acredito que uma modificação na própria marca seja necessária porque se você acabou de configurar uma função com escopo de sessão que aciona manualmente django_db_blocker.unblock()
, isso parece acontecer depois que a marca django_db configurou a primeira transação.
Isso pode ser algo assim (em plugin.py _django_db_marker()
):
if marker:
if marker.session_db:
getfixturevalue(request, 'db_session')
if marker.class_db:
getfixturevalue(request, 'db_class')
validate_django_db(marker)
if marker.transaction:
getfixturevalue(request, 'transactional_db')
else:
getfixturevalue(request, 'db')
Essa conversa é maluca ou vale a pena explorar mais esse tópico?
Sobre a finalização: https://github.com/tipsi/pytest-tipsi-testing/blob/master/tests/test_finalization.py
Este teste não funciona sem finalização explícita, da mesma forma que acessórios de banco de dados não funcionais. E isso é sobre a implementação do pytest, então não há nada a fazer no pytest-django para corrigi-lo.
Duplicata de #388 e #33
Obrigado, fechando.
👍 obrigado!
Eu também apreciaria muito essa funcionalidade.
@mkokotovich
Quantos? O suficiente para fazer isso acontecer sozinho? ;)
De qualquer forma, o problema principal / fundamental aqui (do comentário original) já é que o banco de dados é redefinido durante os testes, portanto, não há uma maneira trivial de ter um acessório com escopo de sessão como esse.
O que pode funcionar é algo junto:
@pytest.fixture(scope="session")
def django_db_setup(django_db_setup, django_db_blocker):
with django_db_blocker.unblock():
with transaction.atomic(): # XXX: could/should use `TestCase_enter_atomics` for multiple dbs
load_time_consuming_db_fixture()
yield
A ideia é envolver tudo em um bloco atômico adicional. Isso não foi testado no entanto, e você pode precisar usar TransactionTestCase
na verdade para isso.
@paultiplady
Seu comentário parece bom - ou seja, vale a pena explorar mais AFAICS (veja também meu comentário anterior).
@blueyed estamos usando essa abordagem por mais de um ano (envolva tudo em um bloco atômico adicional) funciona muito bem.
Mas você não pode simplesmente incluir algo assim porque o pytest não possui o determenístico onde o nível de sessão (e outro superior ao teste) será fechado, portanto, exigirá o rastreamento de dependências de transações de banco de dados, independentemente de exigirem um ao outro ou não diretamente .
Portanto, você precisa rastrear explicitamente a pilha de transações e fechar as transações aninhadas antes do próximo teste. In pode ser feito da seguinte forma: https://github.com/tipsi/pytest-tipsi-django/blob/master/pytest_tipsi_django/django_fixtures.py#L46
desculpe, quero esclarecer, pois acredito que estou enfrentando o mesmo problema:
Se eu quiser criar um fixture que dependa da criação de um novo objeto db, pensei que poderia fazer
@pytest.mark.django_db(transaction=True)
@pytest.fixture(scope="session")
def object():
object = Object.create_object(params)
yield object
// or alternatively
object = mixer.blend('myObject')
yield object
No entanto, recebo o seguinte erro na execução do caso de teste: Database access not allowed, use the "django_db" mark, or the "db" or "transactional_db" fixtures to enable it.
Comentários muito úteis
Este tem sido o maior ponto problemático para mim vindo dos testes baseados em classe UnitTest do Django, para pytest-django -- no Django usamos
setUpTestData
para executar operações de banco de dados caras uma vez (equivalente a fixtures pytest com escopo de sessão), e então há um truque astuto para executarobj.refresh_from_db()
nosetUp
para atualizar as referências de classe.Mesmo se eu estiver apenas criando uma instância de modelo de banco de dados e recarregando-a a cada TC, isso é quase sempre mais rápido do que criar em cada caso de teste.
Seria ótimo se pudéssemos obter os fixtures com escopo de sessão de pytest-tipsi-django mesclados upstream, se isso for uma possibilidade; Demorou um pouco de escavação para encontrar este problema e solução.