So verwenden Sie pytest, um zu überprüfen, ob der Fehler NICHT ausgelöst wird

83

Nehmen wir an, wir haben so etwas:

import py, pytest

ERROR1 = ' --- Error : value < 5! ---'
ERROR2 = ' --- Error : value > 10! ---'

class MyError(Exception):
    def __init__(self, m):
        self.m = m

    def __str__(self):
        return self.m

def foo(i):
    if i < 5:
        raise MyError(ERROR1)
    elif i > 10:
        raise MyError(ERROR2)
    return i


# ---------------------- TESTS -------------------------
def test_foo1():
    with pytest.raises(MyError) as e:
        foo(3)
    assert ERROR1 in str(e)

def test_foo2():
    with pytest.raises(MyError) as e:
        foo(11)
    assert ERROR2 in str(e)

def test_foo3():
        ....
        foo(7)
         ....

F: Wie kann ich test_foo3 () dazu bringen, zu testen, dass kein MyError ausgelöst wird? Es ist offensichtlich, dass ich nur testen konnte:

def test_foo3():
    assert foo(7) == 7

aber ich möchte das über pytest.raises () testen. Ist das irgendwie möglich? Zum Beispiel: In einem Fall hat diese Funktion "foo" überhaupt keinen Rückgabewert.

def foo(i):
    if i < 5:
        raise MyError(ERROR1)
    elif i > 10:
        raise MyError(ERROR2)

Es könnte sinnvoll sein, diesen Weg zu testen, imho.

Paraklet
quelle
Es sieht so aus, als würde man nach einem Problem suchen. Das Testen des Codes foo(7)ist in Ordnung. Sie erhalten die richtige Nachricht und es ist einfacher, mit allen Pytest-Ausgaben zu debuggen. Der Vorschlag, den Sie von @Faruk ( 'Unexpected error...') erzwungen haben, sagt nichts über den Fehler aus und Sie werden stecken bleiben. Das einzige, was Sie tun können, um es besser zu machen, ist Ihre Absicht wie zu erklären test_foo3_works_on_integers_within_range().
Dhill

Antworten:

125

Ein Test schlägt fehl, wenn eine unerwartete Ausnahme ausgelöst wird. Sie können einfach foo (7) aufrufen und haben getestet, dass kein MyError ausgelöst wird. Folgendes wird also ausreichen:

def test_foo3():
    foo(7)

Wenn Sie explizit sein und eine Assert-Anweisung dafür schreiben möchten, können Sie Folgendes tun:

def test_foo3():
    try:
        foo(7)
    except MyError:
        pytest.fail("Unexpected MyError ..")
Faruk Sahin
quelle
3
Danke, es funktioniert, aber es scheint eher ein Hack als eine saubere Lösung zu sein. Beispielsweise schlägt der Test auf foo (4) fehl, jedoch nicht aufgrund eines Bestätigungsfehlers.
Paraklet
Der Test für foo (4) schlägt fehl, da eine Ausnahme ausgelöst wird, die nicht erwartet wurde. Eine andere Möglichkeit wäre, es in einen Try-Catch-Block zu packen und mit einer bestimmten Nachricht fehlzuschlagen. Ich werde meine Antwort aktualisieren.
Faruk Sahin
1
Wenn Sie viele Fälle wie diesen haben, kann es nützlich sein, dies in einer einfachen Funktion zu schreiben: `` `def not_raises (error_class, func, * args, ** kwargs): ...` `` Oder Sie können a schreiben mit-wie Ansatz wie Pytest tut. Wenn Sie dies tun, schlage ich vor, dass Sie eine PR damit schreiben, um allen zu helfen. :) (Das Repository befindet sich in Bitbucket ).
Bruno Oliveira
6
@paraklet - Der Hauptbegriff von pytest lautet "No-Boilerplate-Test" . Es liegt sehr im Sinne von pytest, dass Sie Tests wie in Faruks erstem Beispiel schreiben können, während pytest die Details für Sie erledigt. Das erste Beispiel ist für mich die "saubere Lösung" und das zweite erscheint unnötig ausführlich.
Nick Chammas
21

Aufbauend auf dem, was Oisin erwähnt hat.

Sie können eine einfache not_raisesFunktion erstellen, die ähnlich wie die von pytest funktioniert raises:

from contextlib import contextmanager

@contextmanager
def not_raises(exception):
  try:
    yield
  except exception:
    raise pytest.fail("DID RAISE {0}".format(exception))

Dies ist in Ordnung, wenn Sie sich an raisesein Gegenstück halten möchten und Ihre Tests daher besser lesbar sind. Im Wesentlichen benötigen Sie jedoch nichts anderes, als nur den Codeblock, den Sie testen möchten, in einer eigenen Zeile auszuführen - pytest schlägt ohnehin fehl, sobald dieser Block einen Fehler auslöst.

Pithikos
quelle
1
Ich wünschte, dies wäre in py.test eingebaut. In einigen Fällen würde dies die Lesbarkeit von Tests verbessern. Besonders in Verbindung mit @pytest.mark.parametrize.
Arel
Ich schätze die Lesbarkeit des Codes bei diesem Ansatz sehr!
GrazingScientist
6

Ich war gespannt, ob ein not_raises funktionieren würde. Ein schneller Test hierfür ist (test_notraises.py):

from contextlib import contextmanager

@contextmanager
def not_raises(ExpectedException):
    try:
        yield

    except ExpectedException, err:
        raise AssertionError(
            "Did raise exception {0} when it should not!".format(
                repr(ExpectedException)
            )
        )

    except Exception, err:
        raise AssertionError(
            "An unexpected exception {0} raised.".format(repr(err))
        )

def good_func():
    print "hello"


def bad_func():
    raise ValueError("BOOM!")


def ugly_func():
    raise IndexError("UNEXPECTED BOOM!")


def test_ok():
    with not_raises(ValueError):
        good_func()


def test_bad():
    with not_raises(ValueError):
        bad_func()


def test_ugly():
    with not_raises(ValueError):
        ugly_func()

Es scheint zu funktionieren. Ich bin mir jedoch nicht sicher, ob es im Test wirklich gut liest.

Oisin
quelle