pytest Importmechanismen und sys.path/PYTHONPATH

Importmodi

pytest benötigt als Testframework Importmechanismen für Testmodule und conftest.py Dateien zur Ausführung.

Das Importieren von Dateien in Python ist ein nicht trivialer Prozess, daher können Aspekte des Importprozesses über das Kommandozeilenflag --import-mode gesteuert werden, das folgende Werte annehmen kann:

  • prepend (Standard): Der Verzeichnispfad, der jedes Modul enthält, wird am Anfang von sys.path eingefügt, falls es dort noch nicht vorhanden ist, und dann mit der importlib.import_module Funktion importiert.

    Es wird dringend empfohlen, Ihre Testmodule als Pakete zu organisieren, indem Sie __init__.py Dateien in Ihren Testverzeichnissen hinzufügen. Dadurch werden die Tests Teil eines ordnungsgemäßen Python-Pakets, was es pytest ermöglicht, ihren vollständigen Namen aufzulösen (z. B. tests.core.test_core für test_core.py innerhalb des tests.core Pakets).

    Wenn der Testverzeichnisbaum nicht als Pakete organisiert ist, muss jede Testdatei einen eindeutigen Namen im Vergleich zu anderen Testdateien haben, andernfalls löst pytest einen Fehler aus, wenn es zwei Tests mit demselben Namen findet.

    Dies ist der klassische Mechanismus, der aus der Zeit stammt, als Python 2 noch unterstützt wurde.

  • append: Das Verzeichnis, das jedes Modul enthält, wird am Ende von sys.path angehängt, falls es dort noch nicht vorhanden ist, und mit importlib.import_module importiert.

    Dies ermöglicht es Benutzern besser, Testmodule gegen installierte Versionen eines Pakets auszuführen, auch wenn das zu testende Paket denselben Import-Root hat. Zum Beispiel

    testing/__init__.py
    testing/test_pkg_under_test.py
    pkg_under_test/
    

    die Tests laufen gegen die installierte Version von pkg_under_test, wenn --import-mode=append verwendet wird, während sie mit prepend die lokale Version aufnehmen würden. Diese Art von Verwirrung ist der Grund, warum wir die Verwendung von Src-Layouts befürworten.

    Gleich wie prepend, erfordert es eindeutige Testmodulnamen, wenn der Testverzeichnisbaum nicht in Paketen organisiert ist, da die Module nach dem Import in sys.modules platziert werden.

  • importlib: Dieser Modus verwendet feinere Kontrollmechanismen, die von importlib bereitgestellt werden, um Testmodule zu importieren, ohne sys.path zu ändern.

    Vorteile dieses Modus

    • pytest wird sys.path überhaupt nicht ändern.

    • Testmodulnamen müssen nicht eindeutig sein – pytest generiert automatisch einen eindeutigen Namen basierend auf dem rootdir.

    Nachteile

    • Testmodule können sich nicht gegenseitig importieren.

    • Testdienstprogramme in den Testverzeichnissen (z. B. ein tests.helpers Modul mit testbezogenen Funktionen/Klassen) sind nicht importierbar. Die Empfehlung in diesem Fall ist, testbezogene Dienstprogramme zusammen mit dem Anwendungs-/Bibliothekscode zu platzieren, z. B. app.testing.helpers.

      Wichtig: Mit "Testdienstprogramme" meinen wir Funktionen/Klassen, die direkt von anderen Tests importiert werden; dies schließt keine Fixtures ein, die in conftest.py Dateien platziert werden sollten, zusammen mit den Testmodulen, und die automatisch von pytest erkannt werden.

    So funktioniert es

    1. Gegeben einen bestimmten Modulpfad, z. B. tests/core/test_models.py, wird ein kanonischer Name wie tests.core.test_models abgeleitet und versucht, ihn zu importieren.

      Für Nicht-Testmodule funktioniert dies, wenn sie über sys.path erreichbar sind. Zum Beispiel wird .env/lib/site-packages/app/core.py als app.core importierbar sein. Dies geschieht, wenn Plugins Nicht-Testmodule importieren (z. B. Doctesting).

      Wenn dieser Schritt erfolgreich ist, wird das Modul zurückgegeben.

      Für Testmodule, es sei denn, sie sind von sys.path erreichbar, wird dieser Schritt fehlschlagen.

    2. Wenn der vorherige Schritt fehlschlägt, importieren wir das Modul direkt mit importlib Einrichtungen, was es uns ermöglicht, es zu importieren, ohne sys.path zu ändern.

      Da Python das Modul auch in sys.modules verfügbar haben muss, leitet pytest einen eindeutigen Namen daraus ab, basierend auf seiner relativen Position zum rootdir, und fügt das Modul zu sys.modules hinzu.

      Zum Beispiel wird tests/core/test_models.py als das Modul tests.core.test_models importiert.

    Hinzugefügt in Version 6.0.

Hinweis

Ursprünglich beabsichtigten wir, importlib in zukünftigen Versionen zum Standard zu machen, aber es ist jetzt klar, dass es seine eigenen Nachteile hat, sodass der Standard für absehbare Zeit prepend bleiben wird.

Hinweis

Standardmäßig versucht pytest nicht, Namensraum-Pakete automatisch aufzulösen, aber das kann über die Konfigurationsvariable consider_namespace_packages geändert werden.

Siehe auch

Die Konfigurationsvariable pythonpath.

Die Konfigurationsvariable consider_namespace_packages.

Auswahl eines Testlayouts.

Importmodus-Szenarien prepend und append

Hier ist eine Liste von Szenarien, bei denen die Importmodi prepend oder append verwendet werden und pytest sys.path ändern muss, um Testmodule oder conftest.py Dateien zu importieren, sowie die Probleme, auf die Benutzer dadurch stoßen könnten.

Testmodule / conftest.py Dateien innerhalb von Paketen

Betrachten Sie diese Datei- und Verzeichnisstruktur

root/
|- foo/
   |- __init__.py
   |- conftest.py
   |- bar/
      |- __init__.py
      |- tests/
         |- __init__.py
         |- test_foo.py

Bei Ausführung von

pytest root/

findet pytest foo/bar/tests/test_foo.py und erkennt, dass es Teil eines Pakets ist, da sich eine __init__.py Datei im selben Ordner befindet. Es sucht dann aufwärts, bis es den letzten Ordner findet, der immer noch eine __init__.py Datei enthält, um den Paket-Root (in diesem Fall foo/) zu finden. Um das Modul zu laden, wird root/ an den Anfang von sys.path (falls noch nicht vorhanden) eingefügt, um test_foo.py als das Modul foo.bar.tests.test_foo zu laden.

Die gleiche Logik gilt für die conftest.py Datei: sie wird als foo.conftest Modul importiert.

Die Beibehaltung des vollständigen Paketnamens ist wichtig, wenn Tests in einem Paket leben, um Probleme zu vermeiden und Testmodulen doppelte Namen zu ermöglichen. Dies wird auch ausführlich in Konventionen für die Python-Testentdeckung diskutiert.

Eigenständige Testmodule / conftest.py Dateien

Betrachten Sie diese Datei- und Verzeichnisstruktur

root/
|- foo/
   |- conftest.py
   |- bar/
      |- tests/
         |- test_foo.py

Bei Ausführung von

pytest root/

pytest findet foo/bar/tests/test_foo.py und erkennt, dass es NICHT Teil eines Pakets ist, da sich keine __init__.py Datei im selben Ordner befindet. Es fügt dann root/foo/bar/tests zu sys.path hinzu, um test_foo.py als das Modul test_foo zu importieren. Dasselbe geschieht mit der conftest.py Datei, indem root/foo zu sys.path hinzugefügt wird, um sie als conftest zu importieren.

Aus diesem Grund kann dieses Layout keine Testmodule mit demselben Namen haben, da sie alle im globalen Import-Namespace importiert werden.

Dies wird auch ausführlich in Konventionen für die Python-Testentdeckung diskutiert.

Aufruf von pytest versus python -m pytest

Das Ausführen von pytest mit pytest [...] anstelle von python -m pytest [...] ergibt ein fast äquivalentes Verhalten, mit der Ausnahme, dass letzteres das aktuelle Verzeichnis zu sys.path hinzufügt, was dem Standardverhalten von python entspricht.

Siehe auch Aufruf von pytest über python -m pytest.