Wie man unittest-basierte Tests mit pytest verwendet¶
pytest unterstützt das Ausführen von Python unittest-basierten Tests sofort. Es ist dazu gedacht, bestehende unittest-basierte Test-Suiten zu nutzen, um pytest als Test-Runner zu verwenden und auch die schrittweise Anpassung der Test-Suite zu ermöglichen, um die Funktionen von pytest vollständig zu nutzen.
Um eine bestehende Test-Suite im unittest-Stil mit pytest auszuführen, tippen Sie
pytest tests
pytest sammelt automatisch unittest.TestCase-Unterklassen und ihre test-Methoden in Dateien test_*.py oder *_test.py.
Fast alle unittest-Funktionen werden unterstützt
unittest.skip()/unittest.skipIf()-Stil-Dekoratorenunittest.skip()/unittest.skipIf()unittest.TestCase.setUp()/unittest.TestCase.tearDown()unittest.TestCase.setUp()/unittest.TestCase.tearDown()unittest.TestCase.setUpClass()/unittest.TestCase.tearDownClass()unittest.TestCase.setUpClass()/unittest.TestCase.tearDownClass()unittest.setUpModule()/unittest.tearDownModule()unittest.setUpModule()/unittest.tearDownModule()unittest.TestCase.subTest()(seit Version9.0)unittest.TestCase.subTest()(seit Version9.0)
Bis zu diesem Zeitpunkt hat pytest keine Unterstützung für die folgenden Funktionen
Vorteile sofort¶
Durch die Ausführung Ihrer Test-Suite mit pytest können Sie mehrere Funktionen nutzen, in den meisten Fällen ohne bestehenden Code ändern zu müssen.
Aussagekräftigere Tracebacks erhalten;
Erfassung von stdout und stderr;
Testauswahloptionen mit den Flags
-kund-mTestauswahloptionen mit-kund-mFlags;Die Befehlszeilenoption –pdb zum Debuggen bei Testfehlern (siehe Hinweis unten);
Verteilen von Tests auf mehrere CPUs mit dem Plugin pytest-xdist;
Verwenden von einfachen assert-Anweisungen anstelle von
self.assert*-Funktionen (unittest2pytest ist dabei immens hilfreich);
pytest-Funktionen in unittest.TestCase-Unterklassen¶
Die folgenden pytest-Funktionen funktionieren in unittest.TestCase-Unterklassen
Die folgenden pytest-Funktionen funktionieren **nicht** und werden aufgrund unterschiedlicher Designphilosophien wahrscheinlich auch nie funktionieren.
Drittanbieter-Plugins funktionieren möglicherweise gut oder schlecht, abhängig vom Plugin und der Test-Suite.
Mischen von pytest-Fixtures in unittest.TestCase-Unterklassen mithilfe von Markierungen¶
Das Ausführen Ihrer Unittests mit pytest ermöglicht die Verwendung seines Fixture-Mechanismus mit Tests im unittest.TestCase-Stil. Vorausgesetzt, Sie haben zumindest die pytest-Fixture-Funktionen überflogen, beginnen wir gleich mit einem Beispiel, das eine pytest db_class-Fixture integriert, ein klassenspeicherndes Datenbankobjekt einrichtet und es dann aus einem Unittest-artigen Test referenziert.
# content of conftest.py
# we define a fixture function below and it will be "used" by
# referencing its name from tests
import pytest
@pytest.fixture(scope="class")
def db_class(request):
class DummyDB:
pass
# set a class attribute on the invoking test context
request.cls.db = DummyDB()
Dies definiert eine Fixture-Funktion db_class, die - wenn sie verwendet wird - einmal für jede Testklasse aufgerufen wird und das Klassenattribut db auf eine Instanz von DummyDB setzt. Die Fixture-Funktion erreicht dies, indem sie ein spezielles request-Objekt empfängt, das Zugriff auf den anfordernden Testkontext wie das Attribut cls bietet, was die Klasse bezeichnet, aus der die Fixture verwendet wird. Diese Architektur entkoppelt das Schreiben von Fixtures vom eigentlichen Testcode und ermöglicht die Wiederverwendung der Fixture durch eine minimale Referenz, den Namen der Fixture. Schreiben wir also eine tatsächliche unittest.TestCase-Klasse, die unsere Fixture-Definition verwendet.
# content of test_unittest_db.py
import unittest
import pytest
@pytest.mark.usefixtures("db_class")
class MyTest(unittest.TestCase):
def test_method1(self):
assert hasattr(self, "db")
assert 0, self.db # fail for demo purposes
def test_method2(self):
assert 0, self.db # fail for demo purposes
Der Klassen-Decorator @pytest.mark.usefixtures("db_class") stellt sicher, dass die pytest-Fixture-Funktion db_class einmal pro Klasse aufgerufen wird. Aufgrund der bewusst fehlerhaften Assert-Anweisungen können wir die self.db-Werte im Traceback betrachten.
$ pytest test_unittest_db.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-9.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 2 items
test_unittest_db.py FF [100%]
================================= FAILURES =================================
___________________________ MyTest.test_method1 ____________________________
self = <test_unittest_db.MyTest testMethod=test_method1>
def test_method1(self):
assert hasattr(self, "db")
> assert 0, self.db # fail for demo purposes
^^^^^^^^^^^^^^^^^
E AssertionError: <conftest.db_class.<locals>.DummyDB object at 0xdeadbeef0001>
E assert 0
test_unittest_db.py:11: AssertionError
___________________________ MyTest.test_method2 ____________________________
self = <test_unittest_db.MyTest testMethod=test_method2>
def test_method2(self):
> assert 0, self.db # fail for demo purposes
^^^^^^^^^^^^^^^^^
E AssertionError: <conftest.db_class.<locals>.DummyDB object at 0xdeadbeef0001>
E assert 0
test_unittest_db.py:14: AssertionError
========================= short test summary info ==========================
FAILED test_unittest_db.py::MyTest::test_method1 - AssertionError: <conft...
FAILED test_unittest_db.py::MyTest::test_method2 - AssertionError: <conft...
============================ 2 failed in 0.12s =============================
Dieser standardmäßige pytest-Traceback zeigt, dass sich die beiden Testmethoden dieselbe self.db-Instanz teilen, was unsere Absicht war, als wir die klassenbezogene Fixture-Funktion oben geschrieben haben.
Verwendung von Autouse-Fixtures und Zugriff auf andere Fixtures¶
Obwohl es normalerweise besser ist, die Nutzung benötigter Fixtures für einen bestimmten Test explizit zu deklarieren, möchten Sie möglicherweise manchmal Fixtures haben, die in einem bestimmten Kontext automatisch verwendet werden. Schließlich erzwingt der traditionelle Stil von unittest-Setup die Verwendung dieser impliziten Fixture-Schreibweise, und es besteht die Chance, dass Sie daran gewöhnt sind oder sie mögen.
Sie können Fixture-Funktionen mit @pytest.fixture(autouse=True) kennzeichnen und die Fixture-Funktion in dem Kontext definieren, in dem sie verwendet werden soll. Betrachten wir eine initdir-Fixture, die dafür sorgt, dass alle Testmethoden einer TestCase-Klasse in einem temporären Verzeichnis mit einer vorinitialisierten samplefile.ini ausgeführt werden. Unsere initdir-Fixture verwendet selbst die integrierte pytest-Fixture tmp_path, um die Erstellung eines pro Test temporären Verzeichnisses zu delegieren.
# content of test_unittest_cleandir.py
import unittest
import pytest
class MyTest(unittest.TestCase):
@pytest.fixture(autouse=True)
def initdir(self, tmp_path, monkeypatch):
monkeypatch.chdir(tmp_path) # change to pytest-provided temporary directory
tmp_path.joinpath("samplefile.ini").write_text("# testdata", encoding="utf-8")
def test_method(self):
with open("samplefile.ini", encoding="utf-8") as f:
s = f.read()
assert "testdata" in s
Aufgrund des autouse-Flags wird die initdir-Fixture-Funktion für alle Methoden der Klasse verwendet, in der sie definiert ist. Dies ist eine Abkürzung für die Verwendung einer @pytest.mark.usefixtures("initdir")-Markierung auf der Klasse, wie im vorherigen Beispiel.
Ausführen dieses Testmoduls …
$ pytest -q test_unittest_cleandir.py
. [100%]
1 passed in 0.12s
… ergibt einen bestandenen Test, da die initdir-Fixture-Funktion vor der test_method ausgeführt wurde.
Hinweis
unittest.TestCase-Methoden können keine Fixture-Argumente direkt empfangen, da deren Implementierung wahrscheinlich die Fähigkeit beeinträchtigt, allgemeine unittest.TestCase-Test-Suiten auszuführen.
Die obigen Beispiele für usefixtures und autouse sollten helfen, pytest-Fixtures in Unittest-Suiten zu integrieren.
Sie können auch schrittweise von der Unterklasse von unittest.TestCase zu *einfachen asserts* übergehen und dann Schritt für Schritt die volle Funktionsvielfalt von pytest nutzen.
Hinweis
Aufgrund architektonischer Unterschiede zwischen den beiden Frameworks wird das Setup und Teardown für unittest-basierte Tests während der call-Phase des Testens durchgeführt, anstatt in den standardmäßigen setup- und teardown-Stufen von pytest. Dies kann in einigen Situationen wichtig sein zu verstehen, insbesondere bei der Fehlersuche. Wenn beispielsweise eine unittest-basierte Suite während des Setups Fehler aufweist, meldet pytest während seiner setup-Phase keine Fehler und löst den Fehler stattdessen während des call aus.