Wie kann man richtig behaupten, dass im Pytest eine Ausnahme ausgelöst wird?


292

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)

Ausgabe:

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

Wie erstelle ich ein Pytest-Print-Traceback, damit ich sehe, wo in der whateverFunktion eine Ausnahme ausgelöst wurde?


Ich bekomme den gesamten Traceback, Ubuntu 14.04, Python 2.7.6
thefourtheye

@thefourtheye Machen Sie bitte das Wesentliche mit der Ausgabe. Ich habe es mit Python 2.7.4 und Ubunthu 14.04 versucht - mit dem gleichen Ergebnis wie im Hauptbeitrag beschrieben.
Gill Bates

1
@ GillBates Kannst du die richtige Antwort markieren?
Gonzalo Garcia

Antworten:


330

pytest.raises(Exception) ist was du brauchst.

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

Ausgabe

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

Beachten Sie, dass e_infodas Ausnahmeobjekt gespeichert wird, damit Sie Details daraus extrahieren können. Zum Beispiel, wenn Sie den Ausnahmeaufrufstapel oder eine andere darin verschachtelte Ausnahme überprüfen möchten.


34
Es wäre gut, wenn Sie ein Beispiel einfügen könnten, das tatsächlich abfragt e_info. Für Entwickler, die mit bestimmten anderen Sprachen besser vertraut sind, ist es nicht offensichtlich, dass sich der Umfang von e_infoaußerhalb des withBlocks erstreckt.
cjs

152

Meinst du so etwas:

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.messagehat bei mir nicht funktioniert, musste verwenden str(excinfo.value), fügte eine neue Antwort hinzu
d_j

5
@d_j, assert excinfo.value.args[0] == 'some info'ist der direkte Weg, um auf die Nachricht
zuzugreifen

assert excinfo.match(r"^some info$")funktioniert auch
Robin Nemeth

14
Seit der Version können 3.1Sie das Schlüsselwortargument verwenden match, um zu behaupten, dass die Ausnahme mit einem Text oder einem regulären Ausdruck übereinstimmt: with raises(ValueError, match='must be 0 or None'): raise ValueError("value must be 0 or None")oderwith raises(ValueError, match=r'must be \d+$'): raise ValueError("value must be 42")
Ilya Rusin

58

Es gibt zwei Möglichkeiten, um diese Art von Fällen im Pytest zu behandeln:

  • Mit pytest.raisesFunktion

  • Mit pytest.mark.xfailDekorateur

Verwendung von pytest.raises:

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

Verwendung von pytest.mark.xfail:

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

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

Ausgabe des pytest.xfailMarkers:

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

Wie die Dokumentation sagt:

Die Verwendung pytest.raisesist wahrscheinlich besser für Fälle, in denen Sie Ausnahmen testen, die Ihr eigener Code absichtlich auslöst, während die Verwendung @pytest.mark.xfailmit einer Überprüfungsfunktion wahrscheinlich besser für die Dokumentation nicht behobener Fehler (in denen der Test beschreibt, was passieren sollte) oder Fehler in Abhängigkeiten ist .


xfailist hier nicht die Lösung für das Problem, sondern lässt den Test nur fehlschlagen. Hier möchten wir prüfen, ob eine bestimmte Ausnahme ausgelöst wird.
Strg-C

44

Du kannst es versuchen

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

Um die Ausnahmemeldung / den Ausnahmewert als Zeichenfolge in pytest 5.0.0 abzurufen, str(excinfo.value)ist die Verwendung von erforderlich. Es funktioniert auch in pytest 4.x. Funktioniert in pytest 4.x str(excinfo)ebenfalls, in pytest 5.0.0 jedoch nicht .
Makyen

Das habe ich gesucht. Vielen Dank
Eystein Bye

15

pytest entwickelt sich ständig weiter und mit einer der schönen Änderungen in der jüngeren Vergangenheit ist es jetzt möglich, gleichzeitig zu testen

  • der Ausnahmetyp (strenger Test)
  • die Fehlermeldung (strenge oder lose Prüfung mit einem regulären Ausdruck)

Zwei Beispiele aus der Dokumentation:

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

Ich habe diesen Ansatz in einer Reihe von Projekten verwendet und mag ihn sehr.


6

Der richtige Weg ist die Verwendung, pytest.raisesaber ich habe in den Kommentaren hier einen interessanten alternativen Weg gefunden und möchte ihn für zukünftige Leser dieser Frage speichern:

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


2

Bessere Vorgehensweise ist die Verwendung einer Klasse, die unittest.TestCase erbt und self.assertRaises ausführt.

Beispielsweise:

import unittest


def whatever():
    return 9/0


class TestWhatEver(unittest.TestCase):

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

Dann würden Sie es ausführen, indem Sie Folgendes ausführen:

pytest -vs test_path

4
Besser üben als was? Ich würde die Verwendung der unittest-Syntax anstelle der pytest-Syntax überhaupt nicht als "bessere Praxis" bezeichnen.
Jean-François Corbett

2
Es ist vielleicht nicht "besser", aber es ist eine nützliche Alternative. Da das Kriterium für eine Antwort Nützlichkeit ist, stimme ich zu.
Reb.Cabin

sieht aus wie pytestist populärer als nosex, aber so benutze ich pytest.
Gang

0

Haben Sie versucht, "pytrace = True" zu entfernen?

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

Haben Sie versucht, mit '--fulltrace' zu laufen?

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.