Gute Integrationspraktiken

Paket mit pip installieren

Für die Entwicklung empfehlen wir die Verwendung von venv für virtuelle Umgebungen und pip zur Installation Ihrer Anwendung und aller Abhängigkeiten sowie des pytest-Pakets selbst. Dies stellt sicher, dass Ihr Code und Ihre Abhängigkeiten von Ihrer System-Python-Installation isoliert sind.

Erstellen Sie eine Datei pyproject.toml im Stammverzeichnis Ihres Repositories, wie in Packaging Python Projects beschrieben. Die ersten Zeilen sollten wie folgt aussehen

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "PACKAGENAME"
version = "PACKAGEVERSION"

wobei PACKAGENAME und PACKAGEVERSION der Name und die Version Ihres Pakets sind.

Sie können Ihr Paket dann im „editable“-Modus installieren, indem Sie vom selben Verzeichnis aus ausführen

pip install -e .

wodurch Sie Ihren Quellcode (sowohl Tests als auch Anwendung) ändern und Tests nach Belieben erneut ausführen können.

Konventionen für die Python-Testfindung

pytest implementiert die folgenden Standard-Testfindungsregeln

  • Wenn keine Argumente angegeben sind, beginnt die Sammlung ab testpaths (falls konfiguriert) oder dem aktuellen Verzeichnis. Alternativ können Kommandozeilenargumente in beliebiger Kombination von Verzeichnissen, Dateinamen oder Knoten-IDs verwendet werden.

  • Rekursiv in Verzeichnisse absteigen, es sei denn, sie entsprechen norecursedirs.

  • In diesen Verzeichnissen nach Dateien der Form test_*.py oder *_test.py suchen, die unter ihrem Testpaketnamen importiert werden.

  • Aus diesen Dateien Testelemente sammeln

    • mit test beginnende Testfunktionen oder -methoden außerhalb einer Klasse.

    • mit test beginnende Testfunktionen oder -methoden innerhalb von Test beginnenden Testklassen (ohne eine __init__ Methode). Methoden, die mit @staticmethod und @classmethods dekoriert sind, werden ebenfalls berücksichtigt.

Beispiele, wie Sie Ihre Testfindung anpassen können, finden Sie unter Ändern der Standard-Testfindung (Python).

Innerhalb von Python-Modulen entdeckt pytest Tests auch mit der Standardtechnik des Erbens von unittest.TestCase.

Auswahl eines Testlayouts

pytest unterstützt zwei gängige Testlayouts

Tests außerhalb des Anwendungscodes

Tests in ein zusätzliches Verzeichnis außerhalb Ihres eigentlichen Anwendungscodes zu legen, kann nützlich sein, wenn Sie viele funktionale Tests haben oder aus anderen Gründen Tests vom eigentlichen Anwendungscode trennen möchten (oft eine gute Idee).

pyproject.toml
src/
    mypkg/
        __init__.py
        app.py
        view.py
tests/
    test_app.py
    test_view.py
    ...

Dies hat die folgenden Vorteile

  • Ihre Tests können gegen eine installierte Version ausgeführt werden, nachdem Sie pip install . ausgeführt haben.

  • Ihre Tests können gegen die lokale Kopie mit einer editierbaren Installation ausgeführt werden, nachdem Sie pip install --editable . ausgeführt haben.

Für neue Projekte empfehlen wir die Verwendung des importlib Importmodus (siehe which-import-mode für eine detaillierte Erklärung). Fügen Sie dazu Folgendes zu Ihrer Konfigurationsdatei hinzu

# content of pytest.toml
[pytest]
addopts = ["--import-mode=importlib"]

Generell, aber insbesondere wenn Sie den Standard-Importmodus prepend verwenden, wird dringend empfohlen, ein src-Layout zu verwenden. Hier befindet sich das Stammverzeichnis Ihres Anwendungspakets in einem Unterverzeichnis Ihres Stammverzeichnisses, d.h. src/mypkg/ anstelle von mypkg.

Dieses Layout verhindert viele häufige Fallstricke und hat viele Vorteile, die in diesem ausgezeichneten Blogbeitrag von Ionel Cristian Mărieș besser erklärt werden.

Hinweis

Wenn Sie keine editierbare Installation verwenden und das src-Layout wie oben beschrieben verwenden, müssen Sie den Suchpfad von Python für Moduldateien erweitern, um die Tests direkt gegen die lokale Kopie auszuführen. Sie können dies ad hoc tun, indem Sie die Umgebungsvariable PYTHONPATH setzen

PYTHONPATH=src pytest

oder permanent, indem Sie die Konfigurationsvariable pythonpath verwenden und Folgendes zu Ihrer Konfigurationsdatei hinzufügen

[pytest]
pythonpath = ["src"]
[pytest]
pythonpath = src

Hinweis

Wenn Sie keine editierbare Installation verwenden und nicht das src-Layout verwenden (mypkg direkt im Stammverzeichnis), können Sie sich darauf verlassen, dass Python standardmäßig das aktuelle Verzeichnis in sys.path aufnimmt, um Ihr Paket zu importieren und python -m pytest auszuführen, um die Tests direkt gegen die lokale Kopie auszuführen.

Weitere Informationen zum Unterschied zwischen dem Aufruf von pytest und python -m pytest finden Sie unter Aufruf von pytest versus python -m pytest.

Tests als Teil des Anwendungscodes

Das Einbetten von Testverzeichnissen in Ihr Anwendungspaket ist nützlich, wenn Sie eine direkte Beziehung zwischen Tests und Anwendungsmodulen haben und diese zusammen mit Ihrer Anwendung verteilen möchten.

pyproject.toml
[src/]mypkg/
    __init__.py
    app.py
    view.py
    tests/
        __init__.py
        test_app.py
        test_view.py
        ...

In diesem Schema ist es einfach, Ihre Tests mit der Option --pyargs auszuführen

pytest --pyargs mypkg

pytest findet heraus, wo mypkg installiert ist, und sammelt von dort die Tests.

Beachten Sie, dass dieses Layout auch in Verbindung mit dem im vorherigen Abschnitt erwähnten src-Layout funktioniert.

Hinweis

Sie können Namespace-Pakete (PEP420) für Ihre Anwendung verwenden, aber pytest führt weiterhin die Testpaketnamenfindung basierend auf der Anwesenheit von __init__.py-Dateien durch. Wenn Sie eines der beiden empfohlenen Dateisystemlayouts oben verwenden, aber die __init__.py-Dateien aus Ihren Verzeichnissen weglassen, sollte es einfach funktionieren. Bei „eingebetteten Tests“ müssen Sie jedoch absolute Importe verwenden, um auf Ihren Anwendungscode zuzugreifen.

Hinweis

In den Importmodi prepend und append bestimmt pytest, wenn es beim Durchsuchen des Dateisystems eine Testdatei "a/b/test_module.py" findet, den Importnamen wie folgt

  • Bestimmen des basedir: Dies ist das erste „aufwärts“ (in Richtung Wurzel) liegende Verzeichnis, das keine __init__.py enthält. Wenn z.B. sowohl a als auch b eine __init__.py-Datei enthalten, wird das übergeordnete Verzeichnis von a zum basedir.

  • Ausführen von sys.path.insert(0, basedir), um das Testmodul unter dem vollständig qualifizierten Importnamen importierbar zu machen.

  • import a.b.test_module, wobei der Pfad durch Umwandlung von Pfadtrennzeichen / in „.“-Zeichen bestimmt wird. Das bedeutet, Sie müssen die Konvention einhalten, dass Verzeichnis- und Dateinamen direkt den Importnamen entsprechen.

Der Grund für diese etwas entwickelte Importtechnik ist, dass in größeren Projekten mehrere Testmodule möglicherweise voneinander importieren und somit die Ableitung eines kanonischen Importnamens hilft, Überraschungen wie das zweimalige Importieren eines Testmoduls zu vermeiden.

Mit --import-mode=importlib sind die Dinge weniger kompliziert, da pytest sys.path nicht ändern muss, was die Dinge viel weniger überraschend macht.

Auswahl eines Importmodus

Aus historischen Gründen verwendet pytest standardmäßig den prepend Importmodus anstelle des für neue Projekte empfohlenen importlib-Importmodus. Der Grund liegt in der Funktionsweise des prepend-Modus

Da keine Pakete vorhanden sind, aus denen ein vollständiger Paketname abgeleitet werden kann, importiert pytest Ihre Testdateien als *Top-Level*-Module. Die Testdateien im ersten Beispiel (src-Layout) würden als test_app und test_view Top-Level-Module importiert, indem tests/ zu sys.path hinzugefügt wird.

Dies führt zu einem Nachteil im Vergleich zum Importmodus importlib: Ihre Testdateien müssen eindeutige Namen haben.

Wenn Sie Testmodule mit demselben Namen haben müssen, können Sie als Workaround __init__.py-Dateien zu Ihrem tests-Ordner und dessen Unterordnern hinzufügen, um sie in Pakete zu verwandeln

pyproject.toml
mypkg/
    ...
tests/
    __init__.py
    foo/
        __init__.py
        test_view.py
    bar/
        __init__.py
        test_view.py

Jetzt lädt pytest die Module als tests.foo.test_view und tests.bar.test_view, wodurch Sie Module mit demselben Namen haben können. Dies führt jedoch zu einem subtilen Problem: Um die Testmodule aus dem tests-Verzeichnis zu laden, fügt pytest das Stammverzeichnis des Repositories am Anfang von sys.path ein, was den Nebeneffekt hat, dass nun auch mypkg importierbar ist.

Dies ist problematisch, wenn Sie ein Tool wie tox verwenden, um Ihr Paket in einer virtuellen Umgebung zu testen, da Sie die *installierte* Version Ihres Pakets testen möchten und nicht den lokalen Code aus dem Repository.

Der importlib-Importmodus hat keine der oben genannten Nachteile, da sys.path beim Importieren von Testmodulen nicht geändert wird.

tox

Wenn Sie mit Ihrer Arbeit fertig sind und sicherstellen möchten, dass Ihr tatsächliches Paket alle Tests besteht, sollten Sie sich tox, das Automatisierungstool für virtuelle Umgebungen, ansehen. tox hilft Ihnen, virtuelle Umgebungen mit vordefinierten Abhängigkeiten einzurichten und dann einen vordefinierten Testbefehl mit Optionen auszuführen. Es führt Tests gegen das installierte Paket aus und nicht gegen Ihren Quellcode-Checkout, was hilft, Verpackungsfehler zu erkennen.

Nicht über setuptools ausführen

Die Integration mit setuptools wird nicht empfohlen, d.h. Sie sollten weder python setup.py test noch pytest-runner verwenden, und diese könnten in Zukunft nicht mehr funktionieren.

Dies ist veraltet, da es auf veralteten Funktionen von setuptools basiert und Funktionen nutzt, die Sicherheitsmechanismen in pip brechen. Zum Beispiel umgehen „setup_requires“ und „tests_require“ pip --require-hashes. Weitere Informationen und Migrationsanleitungen finden Sie im pytest-runner-Hinweis. Siehe auch pypa/setuptools#1684.

setuptools beabsichtigt, den Testbefehl zu entfernen.

Prüfen mit flake8-pytest-style

Um sicherzustellen, dass pytest korrekt in Ihrem Projekt verwendet wird, kann es hilfreich sein, das flake8-pytest-style flake8-Plugin zu verwenden.

flake8-pytest-style prüft auf häufige Fehler und Verstöße gegen den Codierungsstil in pytest-Code, wie z.B. falsche Verwendung von Fixtures, Testfunktionsnamen und Markern. Durch die Verwendung dieses Plugins können Sie diese Fehler früh im Entwicklungsprozess erkennen und sicherstellen, dass Ihr pytest-Code konsistent und leicht zu warten ist.

Eine Liste der von flake8-pytest-style erkannten Lintings finden Sie auf der PyPI-Seite.

Hinweis

flake8-pytest-style ist kein offizielles pytest-Projekt. Einige der Regeln erzwingen bestimmte Stilentscheidungen, wie z.B. die Verwendung von @pytest.fixture() anstelle von @pytest.fixture, aber Sie können das Plugin an Ihren bevorzugten Stil anpassen.

Verwendung des Strict-Modus von pytest

Hinzugefügt in Version 9.0.

Pytest enthält eine Reihe von Konfigurationsoptionen, die es strenger machen. Die Optionen sind aus Kompatibilitäts- oder anderen Gründen standardmäßig deaktiviert, aber Sie sollten sie aktivieren, wenn Sie können.

Sie können alle Striktheitsoptionen auf einmal aktivieren, indem Sie die Konfigurationsoption strict setzen

[pytest]
strict = true
[pytest]
strict = true

Die Dokumentation zu strict enthält die Optionen, die sie aktiviert, und ihre Auswirkungen.

Wenn pytest in Zukunft neue Striktheitsoptionen hinzufügt, werden diese ebenfalls im Strict-Modus aktiviert. Daher sollten Sie den Strict-Modus nur aktivieren, wenn Sie eine angeheftete/gesperrte Version von pytest verwenden oder wenn Sie proaktiv neue Striktheitsoptionen übernehmen möchten, sobald sie hinzugefügt werden. Wenn Sie nicht möchten, dass neue Optionen automatisch übernommen werden, können Sie die Optionen einzeln aktivieren

[pytest]
strict_config = true
strict_markers = true
strict_parametrization_ids = true
strict_xfail = true
[pytest]
strict_config = true
strict_markers = true
strict_parametrization_ids = true
strict_xfail = true

Wenn Sie den Strict-Modus verwenden möchten, aber Probleme mit einer bestimmten Option haben, können Sie sie einzeln deaktivieren

[pytest]
strict = true
strict_parametrization_ids = false
[pytest]
strict = true
strict_parametrization_ids = false