Comment affirmer correctement qu'une exception est levée dans Pytest?


293

Code:

# coding=utf-8
import pytest


def whatever():
    return 9/0

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

Production:

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

Comment faire pytest print traceback, donc je verrais où dans la whateverfonction une exception a été levée?


Je reçois l'intégralité du traçage, Ubuntu 14.04, Python 2.7.6
thefourtheye

@thefourtheye Faites l'essentiel avec la sortie s'il vous plaît. J'ai essayé avec Python 2.7.4 et Ubunthu 14.04 - avec le même résultat que celui décrit dans le post principal.
Gill Bates

1
@GillBates pouvez-vous marquer la bonne réponse ??
Gonzalo Garcia

Réponses:


331

pytest.raises(Exception) est ce dont vous avez besoin.

Code

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

Production

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

Notez que e_infoenregistre l'objet d'exception afin que vous puissiez en extraire des détails. Par exemple, si vous souhaitez vérifier la pile d'appels d'exception ou une autre exception imbriquée à l'intérieur.


35
Ce serait bien si vous pouviez inclure un exemple de requête e_info. Pour les développeurs plus familiers avec certains autres langages, il n'est pas évident que la portée de e_infos'étende en dehors du withbloc.
cjs

152

Voulez-vous dire quelque chose comme ça:

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.messagen'a pas fonctionné pour moi, a dû utiliser str(excinfo.value), a ajouté une nouvelle réponse
d_j

5
@d_j, assert excinfo.value.args[0] == 'some info'est le moyen direct d'accéder au message
maxschlepzig

assert excinfo.match(r"^some info$")fonctionne aussi
Robin Nemeth

14
Depuis la version, 3.1vous pouvez utiliser l'argument mot-clé matchpour affirmer que l'exception correspond à un texte ou à une expression régulière: with raises(ValueError, match='must be 0 or None'): raise ValueError("value must be 0 or None")ouwith raises(ValueError, match=r'must be \d+$'): raise ValueError("value must be 42")
Ilya Rusin

58

Il existe deux façons de gérer ce type de cas dans pytest:

  • Utilisation de la pytest.raisesfonction

  • Utiliser le pytest.mark.xfaildécorateur

Utilisation de pytest.raises:

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

Utilisation de pytest.mark.xfail:

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

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

Sortie du pytest.xfailmarqueur:

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

Comme le dit la documentation :

L'utilisation pytest.raisesest probablement meilleure dans les cas où vous testez des exceptions que votre propre code génère délibérément, tandis que l'utilisation @pytest.mark.xfailavec une fonction de vérification est probablement meilleure pour quelque chose comme la documentation de bogues non corrigés (où le test décrit ce qui «devrait» se produire) ou des bogues dans les dépendances .


xfailn'est pas la solution au problème ici, il permet simplement au test d'échouer. Ici, nous aimerions vérifier si une certaine exception est levée.
Ctrl-C

44

tu peux essayer

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

Pour obtenir le message / la valeur d'exception sous forme de chaîne dans pytest 5.0.0, l'utilisation str(excinfo.value)est requise. Il fonctionne également dans pytest 4.x. Dans pytest 4.x, str(excinfo)fonctionne également, mais ne fonctionne pas dans pytest 5.0.0.
Makyen

C'est ce que je cherchais. Merci
Eystein Bye

15

pytest évolue constamment et avec l'un des changements intéressants du passé récent, il est maintenant possible de tester simultanément

  • le type d'exception (test strict)
  • le message d'erreur (vérification stricte ou lâche à l'aide d'une expression régulière)

Deux exemples de la documentation:

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

J'ai utilisé cette approche dans un certain nombre de projets et j'aime beaucoup.


6

La bonne façon est d'utiliser, pytest.raisesmais j'ai trouvé une alternative intéressante dans les commentaires ici et je veux l'enregistrer pour les futurs lecteurs de cette question:

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


2

Une meilleure pratique consistera à utiliser une classe qui hérite d'unittest.TestCase et à exécuter self.assertRaises.

Par exemple:

import unittest


def whatever():
    return 9/0


class TestWhatEver(unittest.TestCase):

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

Ensuite, vous l'exécuteriez en exécutant:

pytest -vs test_path

4
Une meilleure pratique que quoi? Je n'appellerais pas du tout utiliser la syntaxe la plus simple au lieu de la syntaxe pytest "meilleure pratique".
Jean-François Corbett

2
Ce n'est peut-être pas «mieux», mais c'est une alternative utile. Parce que le critère de réponse est l'utilité, je vote de manière positive.
Reb.Cabin

ressemble à pytestest plus populaire que nosex, mais voici comment j'utilise pytest.
Gang

0

Avez-vous essayé de supprimer "pytrace = True"?

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

Avez-vous essayé de courir avec '--fulltrace'?

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.