Estoy usando declaraciones de afirmación de Python para que coincida con el comportamiento real y esperado. No tengo control sobre estos, ya que si hay una prueba de error, aborta los casos. Quiero tomar el control del error de afirmación y quiero definir si quiero abortar el caso de prueba en caso de afirmación de falla o no.
También quiero agregar algo como si hay un error de aserción, entonces el caso de prueba debe pausarse y el usuario puede reanudar en cualquier momento.
No tengo idea de cómo hacer esto
Ejemplo de código, estamos usando pytest aquí
import pytest
def test_abc():
a = 10
assert a == 10, "some error message"
Below is my expectation
Cuando afirmar arroja una afirmaciónError, debería tener la opción de pausar el caso de prueba y puedo depurar y luego reanudar. Para pausar y reanudar, usaré el tkinter
módulo. Haré una función de aserción como a continuación
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)
En el futuro, tengo que cambiar cada declaración de aserción con esta función como
import pytest
def test_abc():
a = 10
# Suppose some code and below is the assert statement
_assertCustom("assert a == 10, 'error message'")
Este es un esfuerzo demasiado grande para mí, ya que tengo que hacer cambios en miles de lugares donde he usado la afirmación. ¿Hay alguna manera fácil de hacer eso enpytest
Summary:
Necesito algo donde pueda pausar el caso de prueba en caso de falla y luego reanudarlo después de la depuración. Lo sé tkinter
y esa es la razón por la que lo he usado. Cualquier otra idea será bienvenida
Note
: El código anterior aún no se ha probado. También puede haber pequeños errores de sintaxis
Editar: Gracias por las respuestas. Extendiendo esta pregunta un poco más adelante ahora. ¿Qué pasa si quiero cambiar el comportamiento de afirmar? Actualmente, cuando hay un error de aserción, sale el caso de prueba. ¿Qué pasa si quiero elegir si necesito salir de caso de prueba en caso de fallo de afirmación particular o no? No quiero escribir una función de aserción personalizada como se mencionó anteriormente porque de esta manera tengo que cambiar en varios lugares
assert
pero escriba sus propias funciones de verificación que hagan lo que quiera.pytest
para tus casos de prueba. Es compatible con el uso de aserción y saltarse las pruebas junto con muchas más características que hacen que la escritura de pruebas más fácil.assert cond, "msg"
en su código_assertCustom("assert cond, 'msg'")
? Probablemente unased
frase podría hacerlo.Respuestas:
Está utilizando
pytest
, lo que le brinda amplias opciones para interactuar con las pruebas que fallan. Le ofrece opciones de línea de comandos y varios ganchos para que esto sea posible. Explicaré cómo usar cada uno y dónde puede hacer personalizaciones para satisfacer sus necesidades específicas de depuración.También entraré en opciones más exóticas que le permitirán omitir afirmaciones específicas por completo, si realmente siente que debe hacerlo.
Manejar excepciones, no afirmar
Tenga en cuenta que una prueba fallida normalmente no detiene pytest; solo si habilitó explícitamente decirle que salga después de un cierto número de fallas . Además, las pruebas fallan porque se genera una excepción;
assert
aumenta,AssertionError
pero esa no es la única excepción que hará que una prueba falle. Desea controlar cómo se manejan las excepciones, no modificarlasassert
.Sin embargo, una aserción no va a finalizar la prueba individual. Esto se debe a que una vez que se genera una excepción fuera de un
try...except
bloque, Python desenrolla el marco de la función actual y no hay retroceso en eso.No creo que eso sea lo que quiere, a juzgar por su descripción de sus
_assertCustom()
intentos de volver a ejecutar la afirmación, pero no obstante, analizaré sus opciones más adelante.Depuración post-mortem en pytest con pdb
Para las diversas opciones para manejar fallas en un depurador, comenzaré con el
--pdb
modificador de línea de comandos , que abre el indicador de depuración estándar cuando falla una prueba (salida por brevedad):Con este modificador, cuando una prueba falla, pytest inicia una sesión de depuración post mortem . Esto es esencialmente exactamente lo que querías; para detener el código en el punto de una prueba fallida y abrir el depurador para ver el estado de su prueba. Puede interactuar con las variables locales de la prueba, los globales y los locales y globales de cada cuadro en la pila.
Aquí pytest le da control total sobre si debe salir o no después de este punto: si usa el
q
comando de salida, pytest también sale de la ejecución, el usoc
de continuar devolverá el control a pytest y se ejecutará la siguiente prueba.Usar un depurador alternativo
No está obligado al
pdb
depurador por esto; Puede configurar un depurador diferente con el--pdbcls
interruptor. Cualquier implementaciónpdb.Pdb()
compatible funcionaría, incluida la implementación del depurador de IPython , o la mayoría de los otros depuradores de Python (el depurador de pudb requiere que-s
se use el conmutador o un complemento especial ). El conmutador toma un módulo y una clase, por ejemplo, para usarpudb
podría usar:Puede usar esta función para escribir su propia clase de envoltura
Pdb
que simplemente regrese de inmediato si la falla específica no es algo que le interese.pytest
UtilizaPdb()
exactamente lo mismo quepdb.post_mortem()
hace :Aquí,
t
hay un objeto de rastreo . Cuandop.interaction(None, t)
regresa,pytest
continúa con la siguiente prueba, a menos quep.quitting
se establezca enTrue
(en ese momento, se cierra pytest).Aquí hay un ejemplo de implementación que muestra que estamos rechazando la depuración y regresa de inmediato, a menos que se levante la prueba
ValueError
, guardada comodemo/custom_pdb.py
:Cuando uso esto con la demostración anterior, esta es la salida (nuevamente, elegida por brevedad):
Las introspectivas anteriores
sys.last_type
para determinar si la falla es 'interesante'.Sin embargo, realmente no puedo recomendar esta opción a menos que desee escribir su propio depurador usando tkInter o algo similar. Tenga en cuenta que es una gran empresa.
Fallas de filtrado; elegir y elegir cuándo abrir el depurador
El siguiente nivel es la depuración de pytest y los ganchos de interacción ; Estos son puntos de enlace para las personalizaciones de comportamiento, para reemplazar o mejorar la forma en que pytest normalmente maneja cosas como manejar una excepción o ingresar al depurador a través de
pdb.set_trace()
obreakpoint()
(Python 3.7 o más reciente).La implementación interna de este enlace también es responsable de imprimir el
>>> entering PDB >>>
banner anterior, por lo que usar este enlace para evitar que se ejecute el depurador significa que no verá esta salida en absoluto. Puede tener su propio gancho y luego delegarlo al gancho original cuando un fallo de prueba es 'interesante', y así filtrar los fallos de prueba independientemente del depurador que esté utilizando. Puede acceder a la implementación interna accediendo por nombre ; el complemento de enlace interno para esto se llamapdbinvoke
. Para evitar que se ejecute, debe anular el registro pero guardar una referencia, ¿podemos llamarlo directamente según sea necesario?Aquí hay una implementación de muestra de tal gancho; puede poner esto en cualquiera de las ubicaciones desde las que se cargan los complementos ; Lo puse en
demo/conftest.py
:El complemento anterior usa el
TerminalReporter
complemento interno para escribir líneas en el terminal; esto hace que la salida sea más limpia cuando se usa el formato de estado de prueba compacto predeterminado y le permite escribir cosas en el terminal incluso con la captura de salida habilitada.El ejemplo registra el objeto del complemento con
pytest_exception_interact
gancho a través de otro gancho,pytest_configure()
pero asegurándose de que se ejecute lo suficientemente tarde (usando@pytest.hookimpl(trylast=True)
) para poder anular el registro delpdbinvoke
complemento interno . Cuando se llama al gancho, el ejemplo prueba contra elcall.exceptinfo
objeto ; También puede comprobar el nodo o el informe también.Con el código de ejemplo anterior en su lugar
demo/conftest.py
,test_ham
se ignora eltest_spam
fallo de la prueba , solo el fallo de la prueba, que aumentaValueError
, da como resultado la apertura del mensaje de depuración:Para repetir, el enfoque anterior tiene la ventaja adicional de que puede combinar esto con cualquier depurador que funcione con pytest , incluido pudb, o el depurador de IPython:
También tiene mucho más contexto sobre qué prueba se estaba ejecutando (a través del
node
argumento) y acceso directo a la excepción planteada (a través de lacall.excinfo
ExceptionInfo
instancia).Tenga en cuenta que los complementos específicos del depurador pytest (como
pytest-pudb
opytest-pycharm
) registran su propiopytest_exception_interact
hooksp. Una implementación más completa tendría que recorrer todos los complementos en el administrador de complementos para anular complementos arbitrarios, automáticamente, usandoconfig.pluginmanager.list_name_plugin
yhasattr()
para probar cada complemento.Hacer que los fracasos desaparezcan por completo
Si bien esto le da control total sobre la depuración de prueba fallida, esto deja la prueba como fallida incluso si optó por no abrir el depurador para una prueba determinada. Si desea hacer fallos desaparecen por completo, se puede hacer uso de un gancho diferente:
pytest_runtest_call()
.Cuando pytest ejecuta pruebas, ejecutará la prueba a través del enlace anterior, que se espera que regrese
None
o genere una excepción. A partir de esto, se crea un informe, opcionalmente se crea una entrada de registro, y si la prueba falla,pytest_exception_interact()
se llama al enlace mencionado anteriormente . Entonces, todo lo que necesita hacer es cambiar el resultado que produce este gancho; en lugar de una excepción, simplemente no debería devolver nada en absoluto.La mejor manera de hacerlo es usar una envoltura de gancho . Los envoltorios de ganchos no tienen que hacer el trabajo real, sino que tienen la oportunidad de alterar lo que sucede con el resultado de un gancho. Todo lo que tienes que hacer es agregar la línea:
en su gancho envoltorio aplicación y se obtiene acceso al resultado de gancho , incluyendo la excepción de prueba a través
outcome.excinfo
. Este atributo se establece en una tupla de (tipo, instancia, rastreo) si se produjo una excepción en la prueba. Alternativamente, puede llamaroutcome.get_result()
y usar eltry...except
manejo estándar .Entonces, ¿cómo se pasa una prueba fallida? Tienes 3 opciones básicas:
pytest.xfail()
al contenedor.pytest.skip()
.outcome.force_result()
método ; establezca el resultado en una lista vacía aquí (es decir: el enlace registrado no produjo nada más queNone
), y la excepción se borra por completo.Lo que uses depende de ti. Asegúrese de verificar primero el resultado de las pruebas omitidas y de falla esperada, ya que no necesita manejar esos casos como si la prueba fallara. Puede acceder a las excepciones especiales que generan estas opciones mediante
pytest.skip.Exception
ypytest.xfail.Exception
.Aquí hay un ejemplo de implementación que marca las pruebas fallidas que no aumentan
ValueError
, como omitidas :Cuando se pone en
conftest.py
la salida se convierte en:Usé la
-r a
bandera para aclarar quetest_ham
se omitió ahora.Si reemplaza la
pytest.skip()
llamada conpytest.xfail("[XFAIL] ignoring everything but ValueError")
, la prueba se marca como una falla esperada:y usando
outcome.force_result([])
marcas lo aprobó:Depende de usted cuál cree que se adapta mejor a su caso de uso. For
skip()
yxfail()
yo imité el formato de mensaje estándar (con el prefijo[NOTRUN]
o[XFAIL]
), pero puede usar cualquier otro formato de mensaje que desee.En los tres casos, pytest no abrirá el depurador para pruebas cuyo resultado haya alterado utilizando este método.
Alteración de declaraciones de afirmación individuales
Si desea modificar las
assert
pruebas dentro de una prueba , entonces se está preparando para mucho más trabajo. Sí, esto es técnicamente posible, pero solo reescribiendo el código que Python ejecutará en el momento de la compilación .Cuando lo usas
pytest
, esto ya se está haciendo . Pytest reescribeassert
declaraciones para darle más contexto cuando sus afirmaciones fallan ; vea esta publicación de blog para obtener una buena descripción de lo que se está haciendo exactamente, así como el_pytest/assertion/rewrite.py
código fuente . Tenga en cuenta que ese módulo tiene más de 1k líneas de largo y requiere que comprenda cómo funcionan los árboles de sintaxis abstracta de Python . Si lo hace, podría monopatch ese módulo para agregar sus propias modificaciones allí, incluido el entornoassert
con untry...except AssertionError:
controlador.Sin embargo , no puede simplemente deshabilitar o ignorar las afirmaciones de forma selectiva, porque las declaraciones posteriores podrían depender fácilmente del estado (arreglos de objetos específicos, conjunto de variables, etc.) contra el cual una afirmación omitida estaba destinada a proteger. Si una afirmación prueba que
foo
no es asíNone
, entoncesfoo.bar
se espera que exista una afirmación posterior , entonces simplemente se encontrará con unAttributeError
allí, etc. Si desea seguir esta ruta, siga adelante con la excepción.No voy a entrar en más detalles sobre la reescritura
asserts
aquí, ya que no creo que valga la pena seguir esto, no dada la cantidad de trabajo involucrado, y con la depuración post mortem que le da acceso al estado de la prueba en el falla de punto de afirmación de todos modos .Tenga en cuenta que si desea hacer esto, no necesita usar
eval()
(lo que no funcionaría de todos modos,assert
es una declaración, por lo que necesitaría usarexec()
en su lugar), ni tendría que ejecutar la afirmación dos veces (que puede generar problemas si la expresión utilizada en el estado alterado de aserción). En su lugar, incrustará elast.Assert
nodo dentro de unast.Try
nodo y adjuntará un controlador excepto que use unast.Raise
nodo vacío para volver a generar la excepción que se detectó.Usar el depurador para omitir declaraciones de aserción.
El depurador de Python realmente le permite omitir declaraciones , utilizando el comando
j
/jump
. Si usted sabe por adelantado que una afirmación específica será fallar, puede utilizar esta prescindir de ella. Puede ejecutar sus pruebas con--trace
, lo que abre el depurador al comienzo de cada prueba , luego emitir unj <line after assert>
para omitirlo cuando el depurador está en pausa justo antes de la afirmación.Incluso puedes automatizar esto. Usando las técnicas anteriores, puede crear un complemento de depurador personalizado que
pytest_testrun_call()
gancho para atrapar laAssertionError
excepciónPdb
subclase que establece un punto de interrupción en la línea antes de la afirmación, y ejecuta automáticamente un salto al segundo cuando se alcanza el punto de interrupción, seguido de unac
continuación.O, en lugar de esperar a que falle una aserción, puede automatizar el establecimiento de puntos de interrupción para cada uno
assert
encontrado en una prueba (nuevamente utilizando el análisis de código fuente, puede extraer trivialmente números de línea paraast.Assert
nodos en un AST de la prueba), ejecute la prueba afirmada usando los comandos con guión del depurador y use eljump
comando para omitir la aserción misma. Tendrías que hacer una compensación; ejecute todas las pruebas bajo un depurador (que es lento ya que el intérprete tiene que llamar a una función de rastreo para cada instrucción) o solo aplique esto a las pruebas fallidas y pague el precio de volver a ejecutar esas pruebas desde cero.Tal complemento sería mucho trabajo para crear, no voy a escribir un ejemplo aquí, en parte porque no encajaría en una respuesta de todos modos, y en parte porque no creo que valga la pena . Simplemente abría el depurador y hacía el salto manualmente. Una afirmación fallida indica un error en la prueba en sí o en el código bajo prueba, por lo que también puede centrarse en depurar el problema.
fuente
Puede lograr exactamente lo que desea sin absolutamente ninguna modificación de código con pytest --pdb .
Con tu ejemplo:
Ejecutar con --pdb:
Tan pronto como una prueba falla, puede depurarla con el depurador de python incorporado. Si ha terminado de depurar, puede hacerlo
continue
con el resto de las pruebas.fuente
Si está utilizando PyCharm, puede agregar un punto de interrupción de excepción para pausar la ejecución cada vez que falla una aserción. Seleccione Ver puntos de interrupción (CTRL-SHIFT-F8) y agregue un controlador de excepciones en aumento para AssertionError. Tenga en cuenta que esto puede ralentizar la ejecución de las pruebas.
De lo contrario, si no le importa detenerse al final de cada prueba fallida (justo antes de que se produzca un error) en lugar de hacerlo en el momento en que falla la afirmación, entonces tiene algunas opciones. Sin embargo, tenga en cuenta que en este punto ya podrían haberse ejecutado varios códigos de limpieza, como el cierre de archivos que se abrieron en la prueba. Las opciones posibles son:
Puede decirle a pytest que lo coloque en el depurador de errores utilizando la opción --pdb .
Puede definir el siguiente decorador y decorar cada función de prueba relevante con él. (Además de registrar un mensaje, también puede iniciar un pdb.post_mortem en este punto, o incluso un código interactivo. Interactuar con los locales del marco donde se originó la excepción, como se describe en esta respuesta ).
fuente
pause_on_assert
para leer desde un archivo para decidir si hacer una pausa o no.Una solución simple, si está dispuesto a usar Visual Studio Code, podría ser usar puntos de interrupción condicionales .
Esto le permitiría configurar sus afirmaciones, por ejemplo:
Luego agregue un punto de interrupción condicional en su línea de afirmación que solo se romperá cuando su afirmación falle:
fuente