Estou usando instruções de declaração Python para corresponder ao comportamento real e esperado. Eu não tenho controle sobre eles como se houvesse um caso de erro de abortamento. Desejo assumir o controle do erro de asserção e quero definir se quero interromper o testcase ao declarar falha ou não.
Também quero adicionar algo como, se houver um erro de asserção, o caso de teste deve ser pausado e o usuário pode retomar a qualquer momento.
Eu não tenho nenhuma idéia de como fazer isso
Exemplo de código, estamos usando pytest aqui
import pytest
def test_abc():
a = 10
assert a == 10, "some error message"
Below is my expectation
Quando assert lança um assertionError, eu deveria ter a opção de pausar o testcase e pode depurar e continuar mais tarde. Para pausar e retomar, usarei o tkinter
módulo. Vou fazer uma função de afirmação como abaixo
import tkinter
import tkinter.messagebox
top = tkinter.Tk()
def _assertCustom(assert_statement, pause_on_fail = 0):
#assert_statement will be something like: assert a == 10, "Some error"
#pause_on_fail will be derived from global file where I can change it on runtime
if pause_on_fail == 1:
try:
eval(assert_statement)
except AssertionError as e:
tkinter.messagebox.showinfo(e)
eval (assert_statement)
#Above is to raise the assertion error again to fail the testcase
else:
eval (assert_statement)
No futuro, tenho que alterar todas as declarações assert com esta função como
import pytest
def test_abc():
a = 10
# Suppose some code and below is the assert statement
_assertCustom("assert a == 10, 'error message'")
Isso é muito esforço para mim, pois tenho que fazer alterações em milhares de lugares onde já afirmei. Existe alguma maneira fácil de fazer isso empytest
Summary:
Preciso de algo em que possa pausar o testcase em caso de falha e depois retomar após a depuração. Eu sei tkinter
e é por isso que eu o usei. Quaisquer outras idéias serão bem-vindas
Note
: O código acima ainda não foi testado. Também pode haver pequenos erros de sintaxe
Edit: Obrigado pelas respostas. Estendendo esta questão um pouco à frente agora. E se eu quiser mudar o comportamento de afirmar. Atualmente, quando há um erro de asserção, o testcase sai. E se eu quiser escolher se preciso da saída do testcase em uma falha de declaração específica ou não. Eu não quero escrever a função de afirmação personalizada, como mencionado acima, porque dessa maneira eu tenho que mudar em vários lugares
assert
mas escreva suas próprias funções de verificação que fazem o que você deseja.pytest
para seus casos de teste. Ele suporta o uso de testes de afirmação e pulo, além de muitos outros recursos que facilitam a gravação de conjuntos de testes.assert cond, "msg"
códigos_assertCustom("assert cond, 'msg'")
? Provavelmente umased
fila poderia fazê-lo.Respostas:
Você está usando
pytest
, o que oferece amplas opções para interagir com os testes que falharam. Ele fornece opções de linha de comando e vários ganchos para tornar isso possível. Vou explicar como usar cada um e onde você pode fazer personalizações para atender às suas necessidades específicas de depuração.Também abordarei opções mais exóticas que permitiriam ignorar afirmações específicas por completo, se você realmente sentir que deve.
Manipular exceções, não afirmar
Observe que um teste com falha normalmente não para o pytest; somente se você ativou a solicitação explícita de sair após um certo número de falhas . Além disso, os testes falham porque uma exceção é gerada;
assert
aumenta,AssertionError
mas essa não é a única exceção que fará com que um teste falhe! Você deseja controlar como as exceções são tratadas, não as alteradasassert
.No entanto, uma afirmam não vai terminar o teste individual. Isso ocorre porque, quando uma exceção é gerada fora de um
try...except
bloco, o Python desenrola o quadro de função atual e não há como voltar atrás.Não acho que é isso que você deseja, a julgar pela descrição de suas
_assertCustom()
tentativas de refazer a afirmação, mas discutirei suas opções ainda mais adiante.Depuração post-mortem no pytest com pdb
Para as várias opções para lidar com falhas em um depurador, começarei com a
--pdb
opção de linha de comando , que abre o prompt de depuração padrão quando um teste falha (a saída é mais breve):Com essa opção, quando um teste falha, o pytest inicia uma sessão de depuração post-mortem . Isto é essencialmente exatamente o que você queria; para interromper o código no ponto de um teste com falha e abrir o depurador para verificar o estado do seu teste. Você pode interagir com as variáveis locais do teste, os globais e os locais e globais de todos os quadros da pilha.
Aqui, o pytest fornece controle total sobre a saída ou não após esse ponto: se você usar o
q
comando quit, o pytest também encerra a execução, usandoc
for continue retornará o controle para pytest e o próximo teste será executado.Usando um depurador alternativo
Você não está vinculado ao
pdb
depurador para isso; você pode definir um depurador diferente com a--pdbcls
opção Qualquer implementaçãopdb.Pdb()
compatível funcionaria, incluindo a implementação do depurador IPython ou a maioria dos outros depuradores Python (o depurador pudb exige que o-s
switch seja usado ou um plug-in especial ). O switch utiliza um módulo e uma classe, por exemplo, para usar,pudb
você pode usar:Você pode usar esse recurso para escrever sua própria classe wrapper para
Pdb
que simplesmente retorna imediatamente se o fracasso específico não é algo que você está interessado.pytest
UsosPdb()
exatamente comopdb.post_mortem()
faz :Aqui,
t
é um objeto de rastreamento . Quandop.interaction(None, t)
retorna,pytest
continua com o próximo teste, a menos quep.quitting
esteja definido comoTrue
(nesse ponto, o pytest sai).Aqui está um exemplo de implementação que mostra que estamos recusando a depuração e retorna imediatamente, a menos que o teste tenha sido
ValueError
salvo, salvo comodemo/custom_pdb.py
:Quando eu uso isso com a demonstração acima, esta é a saída (novamente, elidida por questões de brevidade):
As introspecções acima
sys.last_type
para determinar se a falha é 'interessante'.No entanto, eu realmente não posso recomendar esta opção, a menos que você queira escrever seu próprio depurador usando tkInter ou algo semelhante. Observe que essa é uma grande empresa.
Falhas na filtragem; escolha e escolha quando abrir o depurador
O próximo nível é os pytest de depuração e de interacção ganchos ; esses são pontos de gancho para personalizações de comportamento, para substituir ou aprimorar como o pytest normalmente lida com coisas como lidar com uma exceção ou entrar no depurador via
pdb.set_trace()
oubreakpoint()
(Python 3.7 ou mais recente).A implementação interna desse gancho também é responsável por imprimir o
>>> entering PDB >>>
banner acima; portanto, usar esse gancho para impedir a execução do depurador significa que você não verá essa saída. Você pode ter seu próprio gancho e delegá-lo ao gancho original quando uma falha de teste é 'interessante', para filtrar as falhas de teste independentemente do depurador que você está usando! Você pode acessar a implementação interna acessando-a pelo nome ; o plugin de gancho interno para isso é nomeadopdbinvoke
. Para impedir que ele seja executado, é necessário cancelar o registro , mas salvar uma referência, podemos chamá-lo diretamente, conforme necessário.Aqui está uma implementação de exemplo desse gancho; você pode colocar isso em qualquer um dos locais em que os plugins são carregados ; Coloquei em
demo/conftest.py
:O plugin acima usa o
TerminalReporter
plugin interno para escrever linhas no terminal; isso torna a saída mais limpa ao usar o formato padrão de status de teste compacto e permite gravar coisas no terminal, mesmo com a captura de saída ativada.O exemplo registra o objeto de plug-in com
pytest_exception_interact
gancho por outro gancho,pytest_configure()
mas certificando-se de que seja executado com atraso suficiente (usando@pytest.hookimpl(trylast=True)
) para poder cancelar o registro dopdbinvoke
plug-in interno . Quando o gancho é chamado, o exemplo é testado em relação aocall.exceptinfo
objeto ; você também pode verificar o nó ou o relatório .Com o código de exemplo acima no lugar
demo/conftest.py
, atest_ham
falha de teste é ignorada, apenas atest_spam
falha de teste, que aumentaValueError
, resulta na abertura do prompt de depuração:Para reiterar, a abordagem acima tem a vantagem adicional de poder combinar isso com qualquer depurador que funcione com o pytest , incluindo pudb, ou o depurador IPython:
Ele também tem muito mais contexto sobre qual teste estava sendo executado (por meio do
node
argumento) e acesso direto à exceção gerada (por meio dacall.excinfo
ExceptionInfo
instância).Observe que plugins específicos para depurador pytest (como
pytest-pudb
oupytest-pycharm
) registram seus própriospytest_exception_interact
hooksp. Uma implementação mais completa teria que passar por todos os plugins no gerenciador de plugins para substituir plugins arbitrários, automaticamente, usandoconfig.pluginmanager.list_name_plugin
ehasattr()
para testar cada plug-in.Fazendo as falhas desaparecerem completamente
Embora isso ofereça controle total sobre a depuração de teste com falha, isso ainda deixa o teste com falha, mesmo se você optou por não abrir o depurador para um determinado teste. Se você quiser fazer as falhas desaparecem por completo, você pode fazer uso de um gancho diferente:
pytest_runtest_call()
.Quando o pytest executa testes, ele é executado através do gancho acima, que deve retornar
None
ou gerar uma exceção. A partir disso, um relatório é criado, opcionalmente, uma entrada de log é criada e, se o teste falhar, opytest_exception_interact()
gancho mencionado acima é chamado. Então, tudo que você precisa fazer é mudar o resultado que esse gancho produz; em vez de uma exceção, ele simplesmente não deve retornar nada.A melhor maneira de fazer isso é usar um invólucro de gancho . Os invólucros de gancho não precisam fazer o trabalho real, mas têm a chance de alterar o que acontece com o resultado de um gancho. Tudo que você precisa fazer é adicionar a linha:
na implementação do wrapper de gancho e você obtém acesso ao resultado do gancho , incluindo a exceção de teste via
outcome.excinfo
. Este atributo é definido como uma tupla de (tipo, instância, rastreamento) se uma exceção foi gerada no teste. Como alternativa, você pode ligaroutcome.get_result()
e usar otry...except
manuseio padrão .Então, como você faz um teste reprovado? Você tem 3 opções básicas:
pytest.xfail()
o wrapper.pytest.skip()
.outcome.force_result()
método ; defina o resultado como uma lista vazia aqui (ou seja: o gancho registrado não produziu nada além deNone
) e a exceção é totalmente limpa.O que você usa depende de você. Verifique o resultado dos testes ignorados e de falha esperada primeiro, pois você não precisa lidar com esses casos como se o teste falhasse. Você pode acessar as exceções especiais que essas opções geram via
pytest.skip.Exception
epytest.xfail.Exception
.Aqui está um exemplo de implementação que marca testes com falha que não são disparados
ValueError
, como ignorados :Quando colocado na
conftest.py
saída se torna:Usei a
-r a
bandeira para deixar mais claro o quetest_ham
foi ignorado agora.Se você substituir a
pytest.skip()
chamada porpytest.xfail("[XFAIL] ignoring everything but ValueError")
, o teste será marcado como uma falha esperada:e usando as
outcome.force_result([])
marcas como passadas:Depende de você qual deles você se encaixa melhor no seu caso de uso. Para
skip()
exfail()
eu imitamos o formato de mensagem padrão (prefixado com[NOTRUN]
ou[XFAIL]
), mas você pode usar qualquer outro formato de mensagem que desejar.Nos três casos, o pytest não abrirá o depurador para testes cujo resultado você alterou usando esse método.
Alterando declarações de afirmação individuais
Se você deseja alterar os
assert
testes dentro de um teste , está se preparando para muito mais trabalho. Sim, isso é tecnicamente possível, mas apenas reescrevendo o próprio código que o Python executará no momento da compilação .Quando você usa
pytest
, isso já está sendo feito . Pytest reescreveassert
instruções para fornecer mais contexto quando suas declarações falham ; consulte esta postagem no blog para obter uma boa visão geral do que está sendo feito, bem como do_pytest/assertion/rewrite.py
código-fonte . Observe que esse módulo tem mais de 1k linhas de comprimento e requer que você entenda como as árvores de sintaxe abstrata do Python funcionam. Se você fizer isso, você poderia monkeypatch esse módulo para adicionar suas próprias modificações lá, incluindo em torno doassert
com umtry...except AssertionError:
manipulador.No entanto , você não pode simplesmente desabilitar ou ignorar afirmações seletivamente, porque as instruções subsequentes podem depender facilmente do estado (arranjos de objetos específicos, conjunto de variáveis etc.) aos quais uma afirmação ignorada deveria se proteger. Se uma afirmação testa isso
foo
nãoNone
, então uma afirmação posterior depende dafoo.bar
existência, então você simplesmente se deparará com umaAttributeError
aí, etc. Continue repetindo a exceção, se precisar seguir esse caminho.Não vou entrar em mais detalhes sobre a reescrita
asserts
aqui, pois acho que não vale a pena prosseguir, sem a quantidade de trabalho envolvida e com a depuração post-mortem, dando a você acesso ao estado do teste no ponto de falha de afirmação de qualquer maneira .Observe que, se você quiser fazer isso, não precisará usar
eval()
(o que não funcionaria de qualquer maneira,assert
é uma instrução, portanto, você precisará usá-loexec()
), nem precisará executar a asserção duas vezes (que pode levar a problemas se a expressão usada no estado da asserção for alterada). Em vez disso, você deve incorporar oast.Assert
nó dentro de umast.Try
nó e anexar um manipulador de exceção que usa umast.Raise
nó vazio novamente a exceção capturada.Usando o depurador para pular instruções de asserção.
O depurador Python, na verdade, permite pular instruções usando o comando
j
/jump
. Se você sabe de antemão que uma afirmação específica irá falhar, você pode usar isso para ignorá-lo. Você pode executar seus testes com--trace
, o que abre o depurador no início de cada teste e , em seguida, emite umj <line after assert>
para ignorá-lo quando o depurador é pausado pouco antes da declaração.Você pode até automatizar isso. Usando as técnicas acima, você pode criar um plug-in de depurador personalizado que
pytest_testrun_call()
gancho para capturar aAssertionError
exceçãoPdb
subclasse que define um ponto de interrupção na linha antes da declaração e executa automaticamente um salto para o segundo quando o ponto de interrupção é atingido, seguido de umac
continuação.Ou, em vez de esperar por uma afirmação falhar, você pode automatizar a definição de pontos de interrupção para cada um
assert
encontrado em um teste (novamente usando a análise do código-fonte, você pode extrair trivialmente números de linhas deast.Assert
nós em um AST do teste), executar o teste declarado usando comandos com script do depurador e use ojump
comando para ignorar a própria afirmação. Você teria que fazer uma troca; execute todos os testes em um depurador (que é lento, pois o intérprete precisa chamar uma função de rastreamento para cada instrução) ou apenas aplique isso a testes com falha e pague o preço de reexecutá-los do zero.Esse plug-in seria muito trabalhoso para criar, não vou escrever um exemplo aqui, em parte porque não caberia em uma resposta de qualquer maneira e em parte porque acho que não vale a pena o tempo . Eu apenas abria o depurador e fazia o salto manualmente. Uma declaração com falha indica um erro no próprio teste ou no código em teste; portanto, você também pode se concentrar apenas na depuração do problema.
fonte
Você pode conseguir exatamente o que deseja, sem absolutamente nenhuma modificação de código com pytest --pdb .
Com o seu exemplo:
Execute com --pdb:
Assim que um teste falha, você pode depurá-lo com o depurador python embutido. Se você tiver concluído a depuração, poderá fazê-lo
continue
com o restante dos testes.fonte
Se você estiver usando PyCharm, poderá adicionar um ponto de interrupção de exceção para pausar a execução sempre que uma declaração falhar. Selecione Exibir pontos de interrupção (CTRL-SHIFT-F8) e adicione um manipulador de exceção em aumento para AssertionError. Observe que isso pode retardar a execução dos testes.
Caso contrário, se você não se importar em fazer uma pausa no final de cada teste com falha (um pouco antes do erro), em vez de no momento em que a afirmação falhar, você terá algumas opções. Observe, no entanto, que, nesse ponto, vários códigos de limpeza, como o fechamento de arquivos que foram abertos no teste, já podem ter sido executados. As opções possíveis são:
Você pode dizer ao pytest para colocá-lo no depurador por erros, usando a opção --pdb .
Você pode definir o decorador a seguir e decorar cada função de teste relevante com ele. (Além de registrar uma mensagem, você também pode iniciar um pdb.post_mortem neste momento ou até mesmo um código interativo. Interagir com os locais do quadro em que a exceção se originou, conforme descrito nesta resposta .)
fonte
pause_on_assert
para ler de um arquivo para decidir se deseja pausar ou não.Uma solução simples, se você estiver disposto a usar o Visual Studio Code, poderá ser usar pontos de interrupção condicionais .
Isso permitiria configurar suas asserções, por exemplo:
Em seguida, adicione um ponto de interrupção condicional em sua linha de asserção que só será interrompida quando sua asserção falhar:
fonte