Come affermare correttamente che un'eccezione viene sollevata in Pytest?


293

Codice:

# coding=utf-8
import pytest


def whatever():
    return 9/0

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

Produzione:

================================ 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 ==============================

Come rendere traceback di stampa pytest, in modo da vedere dove è whateverstata sollevata un'eccezione nella funzione?


Ottengo l'intero traceback, Ubuntu 14.04, Python 2.7.6
thefourtheye,

@thefourtheye Fai l'essenziale con l'uscita per favore. Ho provato con Python 2.7.4 e Ubunthu 14.04 - con lo stesso risultato che ho descritto nel post principale.
Gill Bates,

1
@GillBates puoi contrassegnare la risposta corretta ??
Gonzalo Garcia,

Risposte:


331

pytest.raises(Exception) è quello che ti serve.

Codice

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

Produzione

============================================================================================= 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 ======================================================================================

Si noti che e_infosalva l'oggetto eccezione in modo da poter estrarre i dettagli da esso. Ad esempio, se si desidera controllare lo stack delle chiamate di eccezione o un'altra eccezione nidificata all'interno.


35
Sarebbe bene se si potesse includere un esempio di query e_info. Per gli sviluppatori che hanno familiarità con alcuni altri linguaggi, non è ovvio che l'ambito di estensione si e_infoestende al di fuori del withblocco.
cjs,

152

Intendi qualcosa come questo:

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.messagenon ha funzionato per me, ha dovuto usare str(excinfo.value), ha aggiunto una nuova risposta
d_j

5
@d_j, assert excinfo.value.args[0] == 'some info'è il modo diretto per accedere al messaggio
maxschlepzig

assert excinfo.match(r"^some info$")funziona anche
Robin Nemeth,

14
Dalla versione 3.1è possibile utilizzare l'argomento della parola chiave matchper affermare che l'eccezione corrisponde a un testo o regex: with raises(ValueError, match='must be 0 or None'): raise ValueError("value must be 0 or None")oppurewith raises(ValueError, match=r'must be \d+$'): raise ValueError("value must be 42")
Ilya Rusin,

58

Esistono due modi per gestire questo tipo di casi in Pytest:

  • Utilizzando la pytest.raisesfunzione

  • Usando il pytest.mark.xfaildecoratore

Utilizzo di pytest.raises:

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

Utilizzo di pytest.mark.xfail:

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

Uscita di 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 =============================

Uscita del pytest.xfailmarcatore:

============================= 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=============================

Come dice la documentazione :

L'uso pytest.raisesè probabilmente migliore per i casi in cui si stanno verificando eccezioni che il proprio codice sta deliberatamente sollevando, mentre l'utilizzo @pytest.mark.xfailcon una funzione di controllo è probabilmente migliore per qualcosa come documentare bug non corretti (dove il test descrive cosa "dovrebbe" accadere) o bug nelle dipendenze .


xfailnon è la soluzione al problema qui, consente solo di fallire il test. Qui vorremmo verificare se viene sollevata una determinata eccezione.
Ctrl-C

44

Puoi provare

def test_exception():
    with pytest.raises(Exception) as excinfo:   
        function_that_raises_exception()   
    assert str(excinfo.value) == 'some info' 

Per ottenere il messaggio / valore di eccezione come stringa in pytest 5.0.0, str(excinfo.value)è richiesto l' utilizzo di . Funziona anche in Pytest 4.x. In pytest 4.x, str(excinfo)funziona anche, ma non funziona in pytest 5.0.0.
Makyen,

Questo è quello che stavo cercando. Grazie
Eystein Ciao

15

pytest si evolve costantemente e con uno dei bei cambiamenti nel recente passato è ora possibile testare simultaneamente

  • il tipo di eccezione (test rigoroso)
  • il messaggio di errore (controllo rigoroso o non corretto utilizzando un'espressione regolare)

Due esempi dalla documentazione:

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

Ho usato questo approccio in numerosi progetti e mi piace molto.


6

Il modo giusto sta usando pytest.raisesma ho trovato interessanti modi alternativi nei commenti qui e voglio salvarlo per i futuri lettori di questa domanda:

try:
    thing_that_rasises_typeerror()
    assert False
except TypeError:
    assert True


2

Le migliori pratiche utilizzeranno una classe che eredita unittest.TestCase ed eseguirà self.assertRaises.

Per esempio:

import unittest


def whatever():
    return 9/0


class TestWhatEver(unittest.TestCase):

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

Quindi eseguirlo eseguendo:

pytest -vs test_path

4
Pratica migliore di cosa? Non chiamerei affatto l'uso della sintassi unittest invece della sintassi pytest "best practice".
Jean-François Corbett,

2
Potrebbe non essere "migliore", ma è un'alternativa utile. Poiché il criterio per una risposta è l'utilità, sto votando.
Reb.Cabin

sembra che pytestsia più popolare di nosex, ma è così che uso pytest.
Gang

0

Hai provato a rimuovere "pytrace = True"?

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

Hai provato a correre con '--fulltrace'?

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.