¿Cómo afirmar correctamente que se genera una excepción en pytest?

293

Código:

# coding=utf-8
import pytest


def whatever():
    return 9/0

def test_whatever():
    try:
        whatever()
    except ZeroDivisionError as exc:
        pytest.fail(exc, pytrace=True)

Salida:

================================ test session starts =================================
platform linux2 -- Python 2.7.3 -- py-1.4.20 -- pytest-2.5.2
plugins: django, cov
collected 1 items 

pytest_test.py F

====================================== FAILURES ======================================
___________________________________ test_whatever ____________________________________

    def test_whatever():
        try:
            whatever()
        except ZeroDivisionError as exc:
>           pytest.fail(exc, pytrace=True)
E           Failed: integer division or modulo by zero

pytest_test.py:12: Failed
============================== 1 failed in 1.16 seconds ==============================

¿Cómo hacer pytest print traceback, para ver en qué parte de la whateverfunción se generó una excepción?

Gill Bates
fuente
Obtengo el rastreo completo, Ubuntu 14.04, Python 2.7.6
thefourtheye
@thefourtheye Haga la esencia con salida por favor. Intenté con Python 2.7.4 y Ubunthu 14.04, con el mismo resultado que describí en la publicación principal.
Gill Bates
1
@GillBates, ¿puedes marcar la respuesta correcta?
Gonzalo García

Respuestas:

331

pytest.raises(Exception) es lo que necesitas

Código

import pytest

def test_passes():
    with pytest.raises(Exception) as e_info:
        x = 1 / 0

def test_passes_without_info():
    with pytest.raises(Exception):
        x = 1 / 0

def test_fails():
    with pytest.raises(Exception) as e_info:
        x = 1 / 1

def test_fails_without_info():
    with pytest.raises(Exception):
        x = 1 / 1

# Don't do this. Assertions are caught as exceptions.
def test_passes_but_should_not():
    try:
        x = 1 / 1
        assert False
    except Exception:
        assert True

# Even if the appropriate exception is caught, it is bad style,
# because the test result is less informative
# than it would be with pytest.raises(e)
# (it just says pass or fail.)

def test_passes_but_bad_style():
    try:
        x = 1 / 0
        assert False
    except ZeroDivisionError:
        assert True

def test_fails_but_bad_style():
    try:
        x = 1 / 1
        assert False
    except ZeroDivisionError:
        assert True

Salida

============================================================================================= test session starts ==============================================================================================
platform linux2 -- Python 2.7.6 -- py-1.4.26 -- pytest-2.6.4
collected 7 items 

test.py ..FF..F

=================================================================================================== FAILURES ===================================================================================================
__________________________________________________________________________________________________ test_fails __________________________________________________________________________________________________

    def test_fails():
        with pytest.raises(Exception) as e_info:
>           x = 1 / 1
E           Failed: DID NOT RAISE

test.py:13: Failed
___________________________________________________________________________________________ test_fails_without_info ____________________________________________________________________________________________

    def test_fails_without_info():
        with pytest.raises(Exception):
>           x = 1 / 1
E           Failed: DID NOT RAISE

test.py:17: Failed
___________________________________________________________________________________________ test_fails_but_bad_style ___________________________________________________________________________________________

    def test_fails_but_bad_style():
        try:
            x = 1 / 1
>           assert False
E           assert False

test.py:43: AssertionError
====================================================================================== 3 failed, 4 passed in 0.02 seconds ======================================================================================

Tenga en cuenta que e_infoguarda el objeto de excepción para que pueda extraer detalles de él. Por ejemplo, si desea verificar la pila de llamadas de excepción u otra excepción anidada en su interior.

Murilo Giacometti
fuente
35
Sería bueno si pudieras incluir un ejemplo de consulta e_info. Para los desarrolladores más familiarizados con ciertos otros lenguajes, no es obvio que el alcance de se e_infoextienda fuera del withbloque.
cjs
152

Te refieres a algo como esto:

def test_raises():
    with pytest.raises(Exception) as execinfo:   
        raise Exception('some info')
    # these asserts are identical; you can use either one   
    assert execinfo.value.args[0] == 'some info'
    assert str(execinfo.value) == 'some info'
simpleranchero
fuente
16
excinfo.value.messageno funcionó para mí, tuve que usar str(excinfo.value), agregó una nueva respuesta
d_j
55
@d_j, assert excinfo.value.args[0] == 'some info'es la forma directa de acceder al mensaje
maxschlepzig
assert excinfo.match(r"^some info$")funciona bien
Robin Nemeth
14
Desde la versión 3.1, puede usar el argumento de la palabra clave matchpara afirmar que la excepción coincide con un texto o expresión regular: with raises(ValueError, match='must be 0 or None'): raise ValueError("value must be 0 or None")owith raises(ValueError, match=r'must be \d+$'): raise ValueError("value must be 42")
Ilya Rusin
58

Hay dos formas de manejar este tipo de casos en pytest:

  • Usando la pytest.raisesfunción

  • Usando pytest.mark.xfaildecorador

Uso de pytest.raises:

def whatever():
    return 9/0
def test_whatever():
    with pytest.raises(ZeroDivisionError):
        whatever()

Uso de pytest.mark.xfail:

@pytest.mark.xfail(raises=ZeroDivisionError)
def test_whatever():
    whatever()

Salida de pytest.raises:

============================= test session starts ============================
platform linux2 -- Python 2.7.10, pytest-3.2.3, py-1.4.34, pluggy-0.4.0 -- 
/usr/local/python_2.7_10/bin/python
cachedir: .cache
rootdir: /home/user, inifile:
collected 1 item

test_fun.py::test_whatever PASSED


======================== 1 passed in 0.01 seconds =============================

Salida de pytest.xfailmarcador:

============================= test session starts ============================
platform linux2 -- Python 2.7.10, pytest-3.2.3, py-1.4.34, pluggy-0.4.0 -- 
/usr/local/python_2.7_10/bin/python
cachedir: .cache
rootdir: /home/user, inifile:
collected 1 item

test_fun.py::test_whatever xfail

======================== 1 xfailed in 0.03 seconds=============================

Como dice la documentación :

El uso pytest.raiseses probable que sea mejor para los casos en los que está probando excepciones su propio código está levantando deliberadamente, mientras que el uso @pytest.mark.xfailde una función de control es probablemente mejor para algo así como la documentación de errores no fijadas (en el que el ensayo describe lo que “debe” suceder) o errores en las dependencias .

veri_pudicha_coder
fuente
xfailNo es la solución para el problema aquí, solo permite que la prueba falle. Aquí nos gustaría comprobar si se produce una cierta excepción.
Ctrl-C
44

puedes probar

def test_exception():
    with pytest.raises(Exception) as excinfo:   
        function_that_raises_exception()   
    assert str(excinfo.value) == 'some info' 
DJ
fuente
Para obtener el mensaje / valor de excepción como una cadena en pytest 5.0.0, str(excinfo.value)se requiere el uso. También funciona en pytest 4.x. En pytest 4.x, str(excinfo)también funciona, pero no funciona en pytest 5.0.0.
Makyen
Esto es lo que estaba buscando. Gracias
Eystein Bye
15

Pytest evoluciona constantemente y con uno de los cambios agradables en el pasado reciente, ahora es posible probar simultáneamente

  • el tipo de excepción (prueba estricta)
  • el mensaje de error (verificación estricta o suelta usando una expresión regular)

Dos ejemplos de la documentación:

with pytest.raises(ValueError, match='must be 0 or None'):
    raise ValueError('value must be 0 or None')
with pytest.raises(ValueError, match=r'must be \d+$'):
    raise ValueError('value must be 42')

He estado usando ese enfoque en varios proyectos y me gusta mucho.

Dr. Jan-Philip Gehrcke
fuente
6

Se está utilizando la forma correcta, pytest.raisespero encontré una forma alternativa interesante en los comentarios aquí y quiero guardarla para futuros lectores de esta pregunta:

try:
    thing_that_rasises_typeerror()
    assert False
except TypeError:
    assert True
Arbusto Alexey
fuente
2

Una mejor práctica será usar una clase que herede unittest.TestCase y ejecutar self.assertRaises.

Por ejemplo:

import unittest


def whatever():
    return 9/0


class TestWhatEver(unittest.TestCase):

    def test_whatever():
        with self.assertRaises(ZeroDivisionError):
            whatever()

Entonces lo ejecutarías ejecutando:

pytest -vs test_path
kerbelp
fuente
44
¿Mejor práctica que qué? No llamaría a usar la sintaxis unittest en lugar de la sintaxis pytest "mejor práctica".
Jean-François Corbett
2
Puede que no sea "mejor", pero es una alternativa útil. Como el criterio para una respuesta es la utilidad, estoy votando.
Reb.Cabin
parece que pytestes más popular que nosex, pero así es como uso pytest.
Pandilla
0

¿Has intentado eliminar "pytrace = True"?

pytest.fail(exc, pytrace=True) # before
pytest.fail(exc) # after

¿Has intentado correr con '--fulltrace'?

Loic Pantou
fuente