¿Es posible cambiar el comportamiento de la declaración de afirmación de PyTest en Python?
testing assert (4)
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 fallo 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 un aserción de error, debería tener la opción de pausar el caso de prueba y puedo depurar y luego reanudar.
Para pausar y reanudar,
tkinter
módulo
tkinter
.
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 hacerlo en
pytest
Summary:
necesito algo donde pueda pausar el caso de prueba en caso de falla y luego reanudarlo después de la depuración.
Sé sobre
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
Está utilizando
pytest
, que le brinda amplias opciones para interactuar con las pruebas
pytest
.
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;
AssertionError
plantea
AssertionError
pero esa no es la única excepción que hará que una prueba falle.
Desea controlar cómo se manejan las excepciones, no alterar la
assert
.
Sin embargo, una afirmación fallida finalizará la prueba individual.
Eso es porque 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 desea, a juzgar por su descripción de sus intentos
_assertCustom()
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
de línea de comandos
--pdb
, que abre el indicador de depuración estándar cuando falla una prueba (la salida se elije por brevedad):
$ mkdir demo
$ touch demo/__init__.py
$ cat << EOF > demo/test_foo.py
> def test_ham():
> assert 42 == 17
> def test_spam():
> int("Vikings")
> EOF
$ pytest demo/test_foo.py --pdb
[ ... ]
test_foo.py:2: AssertionError
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> entering PDB >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
> /.../demo/test_foo.py(2)test_ham()
-> assert 42 == 17
(Pdb) q
Exit: Quitting debugger
[ ... ]
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 salir o no después de este punto: si usa el comando
q
quit, entonces pytest también sale de la ejecución, usando
c
para continuar devolverá el control a pytest y se ejecutará la siguiente prueba.
Usar un depurador alternativo
No está obligado al depurador
pdb
para esto;
puede configurar un depurador diferente con el
--pdbcls
.
Cualquier implementación
compatible con
pdb.Pdb()
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 se
use
el
modificador
-s
, o un
complemento especial
).
El conmutador toma un módulo y una clase, por ejemplo, para usar
pudb
podría usar:
$ pytest -s --pdb --pdbcls=pudb.debugger:Debugger
Puede usar esta función para escribir su propia clase de envoltura alrededor de
Pdb
que simplemente regrese de inmediato si la falla específica no es algo que le interese.
pytest
usa
Pdb()
exactamente como
pdb.post_mortem()
:
p = Pdb() p.reset() p.interaction(None, t)
Aquí,
t
es un
objeto de rastreo
.
Cuando
p.interaction(None, t)
regresa,
pytest
continúa con la siguiente prueba, a
menos que
p.quitting
esté establecido en
True
(en ese momento, pytest sale).
Aquí hay un ejemplo de implementación que muestra que declinamos depurar y regresa de inmediato, a menos que la prueba
ValueError
, guardado como
demo/custom_pdb.py
:
import pdb, sys
class CustomPdb(pdb.Pdb):
def interaction(self, frame, traceback):
if sys.last_type is not None and not issubclass(sys.last_type, ValueError):
print("Sorry, not interested in this failure")
return
return super().interaction(frame, traceback)
Cuando uso esto con la demostración anterior, esta es la salida (nuevamente, elegida por brevedad):
$ pytest test_foo.py -s --pdb --pdbcls=demo.custom_pdb:CustomPdb
[ ... ]
def test_ham():
> assert 42 == 17
E assert 42 == 17
test_foo.py:2: AssertionError
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> entering PDB >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Sorry, not interested in this failure
F
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> traceback >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
def test_spam():
> int("Vikings")
E ValueError: invalid literal for int() with base 10: ''Vikings''
test_foo.py:4: ValueError
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> entering PDB >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
> /.../test_foo.py(4)test_spam()
-> int("Vikings")
(Pdb)
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 personalizaciones de comportamiento, para reemplazar o mejorar la forma en que pytest normalmente maneja cosas como manejar una excepción o ingresar el depurador a través de
pdb.set_trace()
o
breakpoint()
(Python 3.7 o más reciente).
La implementación interna de este enlace también es responsable de imprimir el banner
>>> entering PDB >>>
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
pdbinvoke
interno para esto se llama
pdbinvoke
.
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
:
import pytest
@pytest.hookimpl(trylast=True)
def pytest_configure(config):
# unregister returns the unregistered plugin
pdbinvoke = config.pluginmanager.unregister(name="pdbinvoke")
if pdbinvoke is None:
# no --pdb switch used, no debugging requested
return
# get the terminalreporter too, to write to the console
tr = config.pluginmanager.getplugin("terminalreporter")
# create or own plugin
plugin = ExceptionFilter(pdbinvoke, tr)
# register our plugin, pytest will then start calling our plugin hooks
config.pluginmanager.register(plugin, "exception_filter")
class ExceptionFilter:
def __init__(self, pdbinvoke, terminalreporter):
# provide the same functionality as pdbinvoke
self.pytest_internalerror = pdbinvoke.pytest_internalerror
self.orig_exception_interact = pdbinvoke.pytest_exception_interact
self.tr = terminalreporter
def pytest_exception_interact(self, node, call, report):
if not call.excinfo. errisinstance(ValueError):
self.tr.write_line("Sorry, not interested!")
return
return self.orig_exception_interact(node, call, report)
El complemento anterior usa el complemento interno
TerminalReporter
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 el
pytest_exception_interact
través de otro
pytest_configure()
,
pytest_configure()
, pero asegurándose de que se ejecute lo suficientemente tarde (usando
@pytest.hookimpl(trylast=True)
) para poder
pdbinvoke
el registro del complemento interno
pdbinvoke
.
Cuando se llama al gancho, el ejemplo prueba contra el
objeto
call.exceptinfo
;
También puede comprobar el
node
o el
report
también.
Con el código de ejemplo anterior en
demo/conftest.py
, se ignora el
test_spam
prueba
test_spam
, solo el
test_spam
prueba
test_spam
, que aumenta
ValueError
, da como resultado la apertura de la solicitud de depuración:
$ pytest demo/test_foo.py --pdb
[ ... ]
demo/test_foo.py F
Sorry, not interested!
demo/test_foo.py F
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> traceback >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
def test_spam():
> int("Vikings")
E ValueError: invalid literal for int() with base 10: ''Vikings''
demo/test_foo.py:4: ValueError
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> entering PDB >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
> /.../demo/test_foo.py(4)test_spam()
-> int("Vikings")
(Pdb)
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:
$ pytest demo/test_foo.py --pdb --pdbcls=IPython.core.debugger:Pdb
[ ... ]
demo/test_foo.py F
Sorry, not interested!
demo/test_foo.py F
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> traceback >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
def test_spam():
> int("Vikings")
E ValueError: invalid literal for int() with base 10: ''Vikings''
demo/test_foo.py:4: ValueError
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> entering PDB >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
> /.../demo/test_foo.py(4)test_spam()
1 def test_ham():
2 assert 42 == 17
3 def test_spam():
----> 4 int("Vikings")
ipdb>
También tiene mucho más contexto sobre qué prueba se estaba ejecutando (a través del argumento del
node
) y acceso directo a la excepción
call.excinfo
(a través de la instancia
call.excinfo
ExceptionInfo
).
Tenga en cuenta que los complementos específicos del depurador de pytest (como
pytest-pudb
o
pytest-pycharm
) registran su propio
pytest_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, usando
config.pluginmanager.list_name_plugin
y
hasattr()
para probar cada complemento.
Hacer que los fracasos desaparezcan por completo
Si bien esto le da un control total sobre la depuración de prueba fallida, esto todavía deja la prueba como
fallida,
incluso si optó por no abrir el depurador para una prueba determinada.
Si desea que las fallas desaparezcan por completo, puede usar un gancho diferente:
pytest_runtest_call()
.
Cuando pytest ejecuta pruebas, ejecutará la prueba a través del enlace anterior, que se espera que devuelva
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, se llama al
pytest_exception_interact()
mencionado
pytest_exception_interact()
.
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:
outcome = yield
en su implementación de envoltorio de gancho y obtiene acceso al
resultado
del
gancho
, incluida la excepción de prueba a través de
outcome.excinfo
.
Este atributo se establece en una tupla de (tipo, instancia, rastreo) si se produjo una excepción en la prueba.
Alternativamente, puede llamar a
outcome.get_result()
y usar el
try...except
estándar
try...except
manejo.
Entonces, ¿cómo se pasa una prueba fallida? Tienes 3 opciones básicas:
-
Puede marcar la prueba como una falla
esperada
, llamando a
pytest.xfail()
en el contenedor. -
Puede marcar el elemento como
omitido
, lo que pretende que la prueba nunca se ejecutó en primer lugar, llamando a
pytest.skip()
. -
Puede eliminar la excepción, utilizando el
método
outcome.force_result()
; 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
pytest.skip.Exception
estas opciones a través de
pytest.skip.Exception
y
pytest.xfail.Exception
.
Aquí hay un ejemplo de implementación que marca las pruebas fallidas que no
ValueError
, como
omitido
:
import pytest
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_call(item):
outcome = yield
try:
outcome.get_result()
except (pytest.xfail.Exception, pytest.skip.Exception, pytest.exit.Exception):
raise # already xfailed, skipped or explicit exit
except ValueError:
raise # not ignoring
except (pytest.fail.Exception, Exception):
# turn everything else into a skip
pytest.skip("[NOTRUN] ignoring everything but ValueError")
Cuando se pone en
conftest.py
la salida se convierte en:
$ pytest -r a demo/test_foo.py
============================= test session starts =============================
platform darwin -- Python 3.8.0, pytest-3.10.0, py-1.7.0, pluggy-0.8.0
rootdir: ..., inifile:
collected 2 items
demo/test_foo.py sF [100%]
=================================== FAILURES ===================================
__________________________________ test_spam ___________________________________
def test_spam():
> int("Vikings")
E ValueError: invalid literal for int() with base 10: ''Vikings''
demo/test_foo.py:4: ValueError
=========================== short test summary info ============================
FAIL demo/test_foo.py::test_spam
SKIP [1] .../demo/conftest.py:12: [NOTRUN] ignoring everything but ValueError
===================== 1 failed, 1 skipped in 0.07 seconds ======================
Utilicé la bandera
-ra
para
test_ham
que
test_ham
se omitió ahora.
Si reemplaza la llamada
pytest.skip()
con
pytest.xfail("[XFAIL] ignoring everything but ValueError")
, la prueba se marca como un error esperado:
[ ... ]
XFAIL demo/test_foo.py::test_ham
reason: [XFAIL] ignoring everything but ValueError
[ ... ]
y el uso de
outcome.force_result([])
marca como aprobado:
$ pytest -v demo/test_foo.py # verbose to see individual PASSED entries
[ ... ]
demo/test_foo.py::test_ham PASSED [ 50%]
Depende de usted cuál cree que se adapta mejor a su caso de uso.
Para
skip()
y
xfail()
el formato de mensaje estándar (prefijado con
[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 alterar las pruebas de
assert
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 usas
pytest
, esto
ya se está haciendo
.
Pytest
reescribe declaraciones de afirmación 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
código fuente
_pytest/assertion/rewrite.py
.
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í, incluyendo rodear la
try...except AssertionError:
con un
try...except AssertionError:
handler.
Sin embargo
, no puede simplemente deshabilitar o ignorar las afirmaciones de forma selectiva, ya que las declaraciones posteriores podrían depender fácilmente del estado (arreglos de objetos específicos, conjunto de variables, etc.) contra el cual se pretendía proteger una afirmación omitida.
Si una afirmación prueba que
foo
no es
None
, entonces una afirmación posterior se basa en
foo.bar
para existir, entonces simplemente se encontrará con un
AttributeError
allí, etc. Siga con volver a plantear la excepción, si necesita seguir esta ruta .
No voy a entrar en más detalles sobre la reescritura de
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 punto de afirmación falla de
todos modos
.
Tenga en cuenta que si desea hacer esto, no necesita usar
eval()
(que de todos modos no funcionaría,
assert
es una declaración, por lo que necesitaría usar
exec()
lugar), ni tendría ejecutar la aserción dos veces (lo que puede generar problemas si la expresión utilizada en el estado alterado aserción).
En su lugar, incrustaría el nodo
ast.Assert
dentro de un nodo
ast.Try
y adjuntaría un controlador de excepción que utilice un nodo
ast.Raise
vacío
ast.Raise
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 sabe
de antemano
que una afirmación específica fallará, puede usarla para omitirla.
Puede ejecutar sus pruebas con
--trace
, que abre el depurador
al comienzo de cada prueba
, luego emite una
j <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
-
usa el gancho
pytest_testrun_call()
para detectar la excepciónAssertionError
- extrae el número de línea ''ofensiva'' de la línea del rastreo, y quizás con algún análisis de código fuente determina los números de línea antes y después de la afirmación requerida para ejecutar un salto exitoso
-
ejecuta la prueba
nuevamente
, pero esta vez usando una subclase
Pdb
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
continuar.
O, en lugar de esperar a que falle una aserción, puede automatizar el establecimiento de puntos de interrupción para cada aserción encontrada en una prueba (nuevamente utilizando el análisis del código fuente, puede extraer trivialmente números de línea para un
ast.Assert
Nodos de
ast.Assert
en un AST de la prueba) ejecute la prueba afirmada utilizando los comandos con secuencia de comandos del depurador y use el comando de
jump
para omitir la afirmació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 declaració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 errónea 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.
Puede lograr exactamente lo que desea sin absolutamente ninguna modificación de código con pytest --pdb .
Con tu ejemplo:
import pytest
def test_abc():
a = 9
assert a == 10, "some error message"
Ejecutar con --pdb:
py.test --pdb
collected 1 item
test_abc.py F
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> traceback >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
def test_abc():
a = 9
> assert a == 10, "some error message"
E AssertionError: some error message
E assert 9 == 10
test_abc.py:4: AssertionError
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> entering PDB >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
> /private/tmp/a/test_abc.py(4)test_abc()
-> assert a == 10, "some error message"
(Pdb) p a
9
(Pdb)
Tan pronto como una prueba falla, puede depurarla con el depurador de python incorporado.
Si ha terminado de depurar, puede
continue
con el resto de las pruebas.
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 hacer una pausa al final de cada prueba fallida (justo antes de que se produzca un error) en lugar de 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 code.interact interactivo. code.interact con los locales del marco donde se originó la excepción, como se describe en esta respuesta ).
from functools import wraps
def pause_on_assert(test_func):
@wraps(test_func)
def test_wrapper(*args, **kwargs):
try:
test_func(*args, **kwargs)
except AssertionError as e:
tkinter.messagebox.showinfo(e)
# re-raise exception to make the test fail
raise
return test_wrapper
@pause_on_assert
def test_abc()
a = 10
assert a == 2, "some error message"
- Si no desea decorar manualmente cada función de prueba, puede definir un dispositivo de uso automático que inspeccione sys.last_value :
import sys
@pytest.fixture(scope="function", autouse=True)
def pause_on_assert():
yield
if hasattr(sys, ''last_value'') and isinstance(sys.last_value, AssertionError):
tkinter.messagebox.showinfo(sys.last_value)
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:
import pytest
def test_abc():
a = 10
assert a == 10, "some error message"
Luego agregue un punto de interrupción condicional en su línea de afirmación que solo se romperá cuando su afirmación falle: