¿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?


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.


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'

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 .


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' 

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.


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


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

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'?

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.