Pytest-Fixtures

Abgeschlossen

Vorrichtungen sind pytest Hilfsfunktionen, die verwendet werden, um modulare, skalierbare und wartungsfähige Tests zu erstellen. Sie verwenden Geräte zum Einrichten von Vorbedingungen für Tests wie Datenbankverbindungen, Erstellen von Testdaten oder Konfigurieren eines Systemzustands, der erforderlich ist, bevor ein Test ausgeführt werden kann. Sie können außerdem nach dem Ausführen von Tests zur Bereinigung verwendet werden.

Zu den wichtigsten Merkmalen der Pytest-Vorrichtungen gehören:

  • Bereichskontrolle: Vorrichtungen können mithilfe des scope-Parameters so konfiguriert werden, dass sie unterschiedliche Bereiche haben (z. B. function, class, module oder session), die festlegen, wie oft die Vorrichtung aufgerufen wird.
  • Behandeln von Setup und Nachbereitung: Pytest verwaltet den Lebenszyklus von Fixtures und richtet dabei die Fixtures nach Bedarf automatisch ein und bereitet diese nach.
  • Abhängigkeitsinjektion: Vorrichtungen werden als Argumente in Testfunktionen eingefügt, wodurch klar wird, welche Tests auf welche Vorrichtungen angewiesen sind.
  • Wiederverwendbarkeit und Modularität: Leuchten können an einem Ort definiert und in mehreren Testfunktionen, Modulen oder sogar Projekten verwendet werden.

Erstellen einer Fixture für temporäre Dateien

Beim Schreiben von Tests, die mit Dateien interagieren, ist es üblich, temporäre Dateien zu benötigen, die das Dateisystem nach dem Testen nicht überladen. Mit pytest können wir eine Einrichtung erstellen, die eine temporäre Datei einrichtet. Die Einrichtung verwendet das Python-Modul tempfile , um temporäre Dateien sicher zu generieren, um sicherzustellen, dass sie verwendet und gelöscht werden können, ohne dass sich dies auf die lokale Umgebung auswirkt. (In dieser ursprünglichen Version unserer Einrichtung wird die Datei aufgrund der delete=False Kennzeichnung nicht automatisch gelöscht. Später adressieren wir das Löschen von Dateien.)

So sieht die Leuchte aus:

import pytest
import tempfile

@pytest.fixture
def tmp_file():
    def create():
        # Create a named temporary file that persists beyond the function scope
        temp = tempfile.NamedTemporaryFile(delete=False)
        return temp.name
    return create

Bei dieser Einrichtung fungiert tmp_file() als Fixture. Der Name der Fixture hängt davon ab, wie Tests auf sie verweisen. Innerhalb der Vorrichtung erstellt die geschachtelte Funktion create() die Datei nur, wenn sie aufgerufen wird, und nicht bei der Einrichtung der Einrichtung. Dies ermöglicht eine präzise Kontrolle darüber, wann die temporäre Datei erstellt wird, was bei Tests nützlich ist, bei denen Zeit- und Dateistatus kritisch sind.

Innerhalb der geschachtelten Funktion create() wird eine temporäre Datei erstellt und dann der absolute Pfad zu dieser zurückgegeben. Hier ist ein Beispiel dafür, wie ein Test die von uns geschriebene Vorrichtung verwenden könnte:

import os

def test_file(tmp_file):
    path = tmp_file()
    assert os.path.exists(path)

Bei einem Test wird eine Vorrichtung verwendet, indem der Name der Vorrichtung als Argument angegeben wird. Unser einfacher Anwendungsfall kann einfach erweitert werden, indem sie in die Datei schreiben oder Änderungen vornehmen, z. B. Ändern von Berechtigungen oder Besitz.

Geltungsbereichsverwaltung

Bei pytest ist die Verwaltung des Lebenszyklus von Testressourcen durch Setup- und Teardown-Routinen entscheidend für die Aufrechterhaltung sauberer und effizienter Testumgebungen. Sie möchten auch die Integrität von Tests schützen, indem Sie sicherstellen, dass jeder Test mit einem bekannten, konsistenten Zustand beginnt. Standardmäßig werden pytest-Fixtures mit einem function-Geltungsbereich ausgeführt, der das Verhalten auf zwei Arten beeinflusst:

  • Lebenszyklus pro Test: Der Rückgabewert des Vorrichtungs wird für jede Testfunktion neu berechnet, die sie verwendet, und stellt sicher, dass jeder Test mit einem frischen Zustand arbeitet.
  • Bereinigung nach jedem Einsatz: Alle erforderlichen Bereinigungsvorgänge werden nach jedem Test durchgeführt, der die Vorrichtung nutzt.

Pytest ermöglicht es auch, Fixtures breiter zu definieren, um die Leistung und Ressourcennutzung zu optimieren. Die Definition des Geltungsbereichs ist besonders in Situationen wie der Verwaltung des Datenbankzustands oder bei komplexen und zeitaufwändigen Statussetups hilfreich. Die vier verfügbaren Bereiche sind:

  • function: Standardumfang, die Vorrichtung wird einmal pro Test ausgeführt
  • class: Die Fixture wird einmal pro Testklasse ausgeführt.
  • module: Wird einmal für ein Modul ausgeführt.
  • session: Wird einmal pro Testsitzung ausgeführt. Dieser Bereich ist nützlich für teure Vorgänge, die während der gesamten Testsitzung beibehalten werden müssen, z. B. das Initialisieren eines Diensts oder das Starten eines Datenbankservers.

In diesem Fall bedeutet einmalige Ausführung, dass der Rückgabewert zwischengespeichert wird. Eine Fixture mit dem Gültigkeitsbereich "Modul" kann mehrmals in einem Testmodul aufgerufen werden, aber der Rückgabewert ist der des ersten Tests, der sie aufgerufen hat.

So würde die tmp_file()-Fixture mit dem Geltungsbereich „Modul“ aussehen:

import pytest
import tempfile

@pytest.fixture(scope="module")
def tmp_file():
    def create():
        temp = tempfile.NamedTemporaryFile(delete=False)
        return temp.name
    return create

Bereinigungsverwaltung

Der vorherige Code, der die tmp_file Einrichtung angibt, erstellt eine temporäre Datei, behandelt die Bereinigung jedoch nach Abschluss der Tests nicht automatisch. Um sicherzustellen, dass temporäre Dateien nicht zurückgelassen werden, können Sie die Vorrichtung von request Pytest verwenden, um eine Bereinigungsfunktion zu registrieren.

So können Sie die tmp_file-Fixture ändern, damit sie die automatische Bereinigung einschließt:

import pytest
import tempfile
import os

@pytest.fixture(scope="module")
def tmp_file(request):
    # Create a temporary file that persists beyond the function scope
    temp = tempfile.NamedTemporaryFile(delete=False)

    def create():
        # Returns the path of the temporary file
        return temp.name

    def cleanup():
        # Remove the file after the tests are done
        os.remove(temp.name)

    # Register the cleanup function to be called after the last test in the module
    request.addfinalizer(cleanup)

    return create

Durch das Verwenden von request.addfinalizer() und das Weitergeben der geschachtelten cleanup()-Funktion wird die Bereinigung je nach Geltungsbereich aufgerufen. In diesem Fall ist module der Bereich, daher ruft pytest nach allen Tests in einem Modul diese Bereinigungsfunktion auf.

Verwendung von conftest.py

Anstatt Ihre Geräte in Ihre Testdateien einzuteilen, können Sie sie in einer conftest.py Datei speichern. Alle Geräte in conftest.py sind automatisch für Ihre Tests im selben Verzeichnis verfügbar, ohne sie explizit importieren zu müssen.

Erkunden von integrierten Fixtures

Pytest verfügt über viele integrierte Vorrichtungen, die zur Optimierung der Tests entwickelt wurden. Diese Vorrichtungen können die Einrichtung und Bereinigung automatisch verarbeiten, sodass Sie sich auf das Schreiben Ihrer Testfälle statt auf die Testverwaltung konzentrieren können.

Zu den wichtigsten integrierten Einbauvorrichtungen gehören:

  • cache: Wird zum Erstellen und Verwalten des Caches auf Testebene verwendet, was zum Speichern von Daten zwischen Testsitzungen nützlich ist.
  • capsys: Erfasst und ermöglicht die Inspektion von stderr und stdout, was das Prüfen und Testen von Konsolenausgaben erleichtert.
  • tmpdir: Stellt ein temporäres Verzeichnis für Dateien bereit, die während tests erstellt und verwendet werden müssen.
  • monkeypatch: Bietet eine Möglichkeit, das Verhalten und die Werte von Objekten, Funktionen und Ihrer Betriebssystemumgebung sicher zu ändern.

Rolle von Monkeypatching beim Testen

Das Testen von Code, der eng in externe Ressourcen wie Datenbanken oder externe APIs integriert wird, kann aufgrund der beteiligten Abhängigkeiten eine Herausforderung darstellen. Eine Technik, die als Affenpatching bezeichnet wird, umfasst eine vorübergehende Änderung Ihres Systems während der Testläufe, sodass Sie unabhängig von externen Systemen den Zustand und das Verhalten Ihrer Betriebssystemumgebung während des Tests sicher ändern können.

Hier ist ein Beispiel für das Überschreiben der os.path.exists() Funktion mithilfe der monkeypatch Vorrichtung:

import os

def test_os(monkeypatch):
    # Override os.path.exists to always return False
    monkeypatch.setattr('os.path.exists', lambda x: False)
    assert not os.path.exists('/')

Alternativ können Sie die setattr() Methode mit direktem Verweis auf das Objekt und attribut verwenden:

def test_os(monkeypatch):
    # Specify the object and attribute to override
    monkeypatch.setattr(os.path, 'exists', lambda x: False)
    assert not os.path.exists('/')

Neben dem Festlegen von Attributen und Überschreibungsmethoden kann die monkeypatch Einrichtung Umgebungsvariablen festlegen und löschen, Wörterbuchwerte ändern und Systempfade ändern. Die monkeypatch Vorrichtung setzt alle Änderungen nach jedem Test automatisch zurück, aber bei der Verwendung der monkeypatch Vorrichtung sollte dennoch Sorgfalt angewendet werden. Im Folgenden finden Sie einige Gründe, um bei der Verwendung vorsichtig zu sein:

-Codeklarheit und Wartung: Eine übermäßige Nutzung von monkeypatch oder deren komplexe Anwendung kann es erschweren, Tests zu verstehen und zu warten. Wenn Sie Ihre Testergebnisse lesen, ist es möglicherweise nicht sofort klar, wie sich die Komponenten normal verhalten sollen und wie sie zum Testen geändert werden.-Testgültigkeit: Monkeypatching kann manchmal zu Tests führen, die unter künstlichen Bedingungen durchgeführt werden und sich stark von der Produktionsumgebung unterscheiden. Das kann zu einem falschen Sicherheitsempfinden führen, da die Tests erfolgreich abgeschlossen werden können, weil das Verhalten des Systems durch den Test zu stark geändert wurde.-Überlastung der Implementierungsdetails: Tests, die auf Monkeypatching basieren, können eng mit bestimmten Implementierungsdetails des Codes gekoppelt sein, den sie testen. Dadurch können Tests spröde und anfällig für geringfügige Änderungen an der zugrunde liegenden Codebasis sein.-Debugging-Komplexität: Debugging-Tests, die monkeypatch verwenden, können komplexer sein, insbesondere wenn der Patch grundlegende Aspekte des Anwendungsverhaltens ändert. Wenn Sie verstehen, warum ein Test fehlschlägt, ist möglicherweise ein tieferer Einblick in die Änderung der Komponenten während des Tests erforderlich.

Obwohl monkeypatch es sich um ein leistungsfähiges Tool zum Erstellen isolierter und kontrollierter Testumgebungen handelt, sollte es sorgfältig und mit einem klaren Verständnis dafür verwendet werden, wie es sich auf die Testsuite und das Verhalten der Anwendung auswirkt.