Es ist nur eine Tatsache des Lebens, da Code wächst Schließlich müssen Sie anfangen, Mocks zu Ihrer Testsuite hinzuzufügen. Was als süßes kleines Zwei-Klassen-Projekt begann, spricht jetzt mit externen Diensten und Sie können es nicht mehr bequem testen.

Deshalb wird Python mit unittest.mock ausgeliefert, einem leistungsstarken Teil der Standardbibliothek zum Stoppen von Abhängigkeiten und Verspotten von Nebenwirkungen.

unittest.mock ist jedoch nicht besonders intuitiv.,

Ich habe mich oft gefragt, warum mein Go-to-Rezept für einen bestimmten Fall nicht funktioniert, also habe ich dieses Cheatsheet zusammengestellt, um mir und anderen zu helfen, schnell zu arbeiten.

Die Codebeispiele finden Sie im Github-Repository des Artikels. Ich werde Python 3.6 verwenden, wenn Sie 3.2 oder darunter verwenden, müssen Sie das mock PyPI Paket verwenden.,

Die Beispiele werden mit unittest.TestCase – Klassen geschrieben, um sie der Einfachheit halber ohne Abhängigkeiten auszuführen, aber Sie könnten sie als Funktionen mit pytest fast direkt schreiben,unittest.mock funktioniert einwandfrei. Wenn Sie ein pytest – Benutzer sind, empfehle ich Ihnen jedoch, sich die hervorragende pytest-mock – Bibliothek anzusehen.

Der Mittelpunkt desunittest.mock Moduls ist natürlich dieMock Klasse., Das Hauptmerkmal eines Mock – Objekts ist, dass es eine andere Mock – Instanz zurückgibt, wenn:

  • Zugriff auf eines seiner Attribute
  • Aufruf des Objekts selbst

/div>

Dies ist das Standardverhalten, kann jedoch auf verschiedene Arten überschrieben werden. Zum Beispiel können Sie einem Attribut in der Mock einen Wert zuweisen, indem Sie:

  • ihn direkt zuweisen, wie Sie es mit jedem Python-Objekt tun würden.,
  • Verwenden Sie dieconfigure_mock – Methode für eine Instanz.
  • Oder übergeben Sie Schlüsselwortargumente bei der Erstellung an die Klasse Mock.

Um Aufrufe des Scheins zu überschreiben, müssen Sie die Eigenschaft return_value konfigurieren, die auch als Schlüsselwortargument im Initialisierer Mock verfügbar ist., Mock immer wieder den gleichen Wert auf alle Anrufe aus, auch in diesem Fall kann auch konfiguriert werden, indem die side_effect attribute:

  • wenn Sie möchten, um unterschiedliche Werte zurückgeben, auf jeden Anruf, den Sie zuweisen können, eine durchsuchbar zu side_effect.
  • Wenn Sie beim Aufruf des Scheins eine Ausnahme auslösen möchten, können Sie das Ausnahmeobjekt einfach side_effectzuweisen.,

Mit all diesen Tools können wir jetzt Stubs für im Wesentlichen jedes Python-Objekt erstellen, das hervorragend für Eingaben in unser System geeignet ist. Aber was ist mit Ausgaben?

Wenn Sie ein CLI-Programm erstellen, um das gesamte Internet herunterzuladen, möchten Sie wahrscheinlich nicht bei jedem Test das gesamte Internet herunterladen. Stattdessen würde es ausreichen zu behaupten, dass requests.download_internet (keine echte Methode) entsprechend aufgerufen wurde. Mock gibt Ihnen praktische Methoden, dies zu tun.,

Hinweis: in unserem Beispiel assert_called_once fehlgeschlagen, dieser präsentiert einen weiteren wichtigen Aspekt des Mock – Objekte, die Sie aufzeichnen, werden alle Interaktionen mit Ihnen und du kannst dann untersuchen diese Interaktionen.

Zum Beispiel können Sie call_count verwenden, um die Anzahl der Aufrufe an die Mock abzurufen, und call_args oder call_args_list verwenden, um die Argumente für die letzten bzw. alle Aufrufe zu überprüfen.,

Wenn dies zu irgendeinem Zeitpunkt unbequem ist, können Sie die Methode reset_mock verwenden, um die aufgezeichneten Interaktionen zu löschen, beachten Sie, dass die Konfiguration nicht zurückgesetzt wird, nur die Interaktionen.

Abschließend möchte ich MagicMock vorstellen, eine Unterklasse von Mock, die Standard-Magic-oder Dunder-Methoden implementiert. Dies macht MagicMock ideal, um das Klassenverhalten zu verspotten, weshalb es beim Patchen die Standardklasse ist.,

Wir sind jetzt bereit, das zu testende Gerät zu verspotten und zu isolieren. Hier sind einige Rezepte zu beachten:

Patch beim Import

Die Hauptmethode zur Verwendung von unittest.mock besteht darin, Importe im zu testenden Modul mit der Funktion patch zu patchen.

patch fängt import Anweisungen ab, die durch eine Zeichenfolge identifiziert werden (dazu später mehr), und gibt eine Mock Instanz zurück, die Sie mit den oben beschriebenen Techniken vorkonfigurieren können.,

Stellen Sie sich vor, wir möchten diese sehr einfache Funktion testen:

Hinweis Wir importieren os und rufen getcwd auf, um das aktuelle Arbeitsverzeichnis abzurufen. Wir möchten es jedoch nicht in unseren Tests aufrufen, da es für unseren Code nicht wichtig ist und der Rückgabewert zwischen den Umgebungen unterschiedlich sein kann, in denen er ausgeführt wird.

Wie oben erwähnt, müssen wir patch mit einer Zeichenfolge versehen, die unseren spezifischen Import darstellt., Wir möchten nicht einfach os.getcwd da dies es für alle Module patchen würde, stattdessen möchten wir das Modul unter Test import von os , dh work.os . Wenn das Modul importiert wird, wirkt patch magisch und gibt stattdessen eine Mock zurück.,

Alternativ können wir die Decorator-Version von patch verwenden, beachten Sie , dass der Test diesmal einen zusätzlichen Parameter hat: mocked_os zu dem die Mock wird in den Test injiziert.,

patch leitet Schlüsselwortargumente an die Mock – Klasse weiter, um eine return_value zu konfigurieren, fügen wir sie einfach als eine hinzu: p>

Spottklassen

Es ist durchaus üblich, Klassen ganz oder teilweise zu patchen., Helper mit "db"

  • Worker gibt den erwarteten Pfad zurück, der von Helper
  • geliefert wird, um Worker in völliger Isolation müssen wir die gesamte Helper Klasse:

    Beachten Sie die doppelte return_value im Beispiel würde die einfache Verwendung von MockHelper.get_path.return_value nicht funktionieren, da im Code, den wir

    get_path für eine Instanz, nicht für die Klasse selbst.,

    Die Verkettungssyntax ist etwas verwirrend, aber denken Sie daran, dass MagicMock bei Aufrufen __init__ eine weitere MagicMockzurückgibt. Hier konfigurieren wir alle gefälschtenHelper Instanzen, die vonMockHelper erstellt wurden, um das zurückzugeben, was wir bei Aufrufen vonget_path Dies ist die einzige Methode, die uns in unserem Test am Herzen liegt.,

    Class speccing

    Eine Folge der Flexibilität von Mock ist, dass Python, sobald wir eine Klasse verspottet haben, AttributeError, da es einfach neue Instanzen von MagicMock für im Grunde alles zurückgibt. Dies ist normalerweise eine gute Sache, kann aber zu verwirrendem Verhalten und möglicherweise Fehlern führen. Wenn Sie beispielsweise den folgenden Test schreiben:

    wird stillschweigend bestanden, ohne dass der Tippfehler in assrt_called_once vollständig fehlt .,

    Wenn wir außerdem Helper.get_path in Helper.get_folder umbenennen würden, vergessen Sie jedoch, den Aufruf in Worker Unsere Tests bestehen weiterhin:

    Zum Glück Mock kommt mit einem Werkzeug, um diese Fehler zu verhindern, speccing.

    Einfach ausgedrückt, es konfiguriert Mocks vor, um nur auf Methoden zu reagieren, die tatsächlich in der Spezifikationsklasse vorhanden sind., Es gibt verschiedene Möglichkeiten, Spezifikationen zu definieren, aber am einfachsten ist es, einfach autospec=True an den patch – Aufruf zu übergeben, mit dem der Mock so konfiguriert wird, dass er sich wie das verspottete Objekt verhält und bei Bedarf Ausnahmen für fehlende Attribute und falsche Signaturen auslöst., Zum Beispiel:

    Partial class mocking

    Wenn Sie weniger zum Testen in völliger Isolation neigen, können Sie eine Klasse auch teilweise mit patch.objectpatchen

    Hier kann patch.object eine verspottete Version von get_path konfigurieren, wobei der Rest des Verhaltens unberührt bleibt., Natürlich bedeutet dies, dass der Test nicht mehr das ist, was Sie strikt für einen Komponententest halten würden, aber Sie können damit einverstanden sein.

    Integrierte Funktionen und Umgebungsvariablen verspotten

    In den vorherigen Beispielen haben wir es vernachlässigt, einen bestimmten Aspekt unserer einfachen Klasse zu testen, den print – Aufruf. Wenn dies im Kontext Ihrer Anwendung wichtig ist, z. B. bei einem CLI-Befehl, müssen wir Zusicherungen für diesen Aufruf vornehmen.,

    print ist natürlich eine integrierte Funktion in Python, was bedeutet, dass wir sie nicht in unser Modul importieren müssen, was dem widerspricht, was wir oben über das Patchen beim Import besprochen haben., Stellen Sie sich immer noch vor, wir hätten diese etwas kompliziertere Version unserer Funktion:

    Wir können einen Test wie folgt schreiben:

    /div>

    Beachten Sie einige Dinge:

    1. Wir können print problemlos verspotten und behaupten, dass es einen Anruf gab, indem wir der „patch on import“ – Regel folgten. Dies war jedoch eine änderung eingeführt, in 3.,5, vorher mussten Sie zum patch Aufruf hinzufügen, um unittest.mockzu signalisieren, um eine Mock zu erstellen, obwohl kein Import mit der Kennung übereinstimmt.
    2. Wir verwenden patch.dict, um eine temporäre Umgebungsvariable in os.environ, dies ist erweiterbar auf jedes andere Wörterbuch, das wir verspotten möchten.,
    3. Wir verschachteln mehrerepatch Kontextmanager-Aufrufe, verwenden jedoch nuras in der ersten, da es die ist, die wir verwenden müssen, umassert_called_once_with aufzurufen .,

    Wenn Sie Kontextmanager nicht verschachteln möchten, können Sie auch die patch – Aufrufe in das Dekoratorformular schreiben:

    Beachten Sie jedoch, dass die Reihenfolge der Argumente für den Test mit der Stapelreihenfolge der Dekoratoren übereinstimmt, und auch dass patch.dict kein Argument einfügt.,dieses sehr häufige Szenario:

    Sie könnten natürlich eine tatsächliche Fixture-Datei hinzufügen, aber in realen Fällen ist dies möglicherweise keine Option Stattdessen können wir die Ausgabe des Kontextmanagers als StringIO Objekt:

    Hier gibt es nichts Besonderes außer der magischen __enter__ Methode, wir müssen uns nur an die zugrunde liegende Mechanik von Kontextmanagern und einige clevere Verwendung unserer vertrauenswürdigen MagicMock erinnern .,

    Klassenattribute verspotten

    Es gibt viele Möglichkeiten, dies zu erreichen, aber einige sind narrensicher als andere. Angenommen, Sie haben den folgenden Code geschrieben:

    Sie können den Code auf zwei Arten testen:

    1. Wenn der zu testende Code über self.ATTRIBUTE auf das Attribut zugreift, können Sie einfach Folgendes festlegen: das Attribut direkt in der Instanz. Dies ist ziemlich sicher, da die Änderung auf diese einzelne Instanz beschränkt ist.,
    2. Alternativ können Sie das Attribut auch in der importierten Klasse im Test festlegen, bevor Sie eine Instanz erstellen. Dies ändert jedoch das Klassenattribut, das Sie in Ihren Test importiert haben, was sich auf die folgenden Tests auswirken kann, sodass Sie daran denken müssen, es zurückzusetzen.

    Der Hauptnachteil dieser Nicht-Mock-Methode besteht darin, dass Ihre Tests fehlschlagen und der Fehler nicht direkt auf diese Namensfehlerübereinstimmung hinweist, wenn Sie das Attribut zu einem bestimmten Zeitpunkt umbenennen.,

    Um dies zu lösen, können wir patch.object für die importierte Klasse verwenden, die sich beschwert, wenn die Klasse nicht über das angegebene Attribut verfügt.

    Hier sind einige Tests mit jeder Technik:

    Spöttische Klassenhelfer

    Das folgende Beispiel ist die Wurzel vieler Probleme beim Monkeypatching mit Mock. Es zeigt sich normalerweise in reiferen Codebasen, die Frameworks und Helfer bei der Klassendefinition verwenden., Stellen Sie sich zum Beispiel diesen hypothetischen Field Klassenhelfer vor:

    Sein Zweck ist es, ein Attribut in eine andere Klasse zu wickeln und zu verbessern, ein ähnliches Muster, das Sie häufig in ORMs oder Formularbibliotheken sehen. Kümmern Sie sich nicht zu sehr um die Details, beachten Sie nur, dass ein type und ein default Wert übergeben werden.,

    Nehmen Sie nun dieses andere Beispiel des Produktionscodes:

    Diese Klasse verwendet das Attribut Field, indem sie die Klasse country definiert, deren Typ und eine default als erstes Element der COUNTRIES Konstante. Die zu testende Logik ist, dass je nach Land ein Rabatt angewendet wird.,

    Für die wir den folgenden Test schreiben könnten:

    Aber das würde NICHT passieren.,

    Lassen Sie uns durch den Test gehen:

    1. Zuerst patcht es die Standardländer in pricer um eine Liste mit einem einzigen Eintrag zu sein ,
    2. Dies sollte das CountryPricer.country Attribut standardmäßig zu diesem Eintrag machen, da seine Definition default=COUNTRIES,
    3. Es instanziiert dann die CountryPricer und fragt nach dem ermäßigten Preis für !

    Also, was ist Los?,

    Die Hauptursache dafür liegt im Verhalten von Python während des Imports, das am besten in Luciano Ramalhos ausgezeichnetem fließendem Python in Kapitel 21 beschrieben wird:

    Für Klassen ist die Geschichte anders: Zum Zeitpunkt des Imports führt der Interpreter den Body jeder Klasse aus, sogar den body von Klassen, die in anderen Klassen verschachtelt sind. Die Ausführung eines Klassentexts bedeutet, dass die Attribute und Methoden der Klasse definiert sind und dann das Klassenobjekt selbst erstellt wird.,

    Wenden Sie dies auf unser Beispiel an:

    1. Die country Attributs Field Instanz wird erstellt, bevor der Test zum Zeitpunkt des Imports ausgeführt wird,
    2. Python liest den Hauptteil der Klasse und übergibt die COUNTRIES definiert an diesem Punkt der Field Instanz,
    3. Unser Testcode wird ausgeführt, aber es ist zu spät für uns, COUNTRIES zu patchen und eine ordnungsgemäße Assertion zu erhalten.,

    Aus der obigen Beschreibung können Sie versuchen, den Import des Moduls zu verzögern, bis innerhalb der Tests etwas wie:

    Dies wird jedoch immer noch nicht übergeben, da mock.patch das Modul importiert und es dann monkeypatch, dies führt zu der gleichen Situation wie zuvor.,

    Um dies zu umgehen, müssen wir den Status der CountryPricer – Klasse zum Zeitpunkt des Tests annehmen und default auf die bereits initialisierte Field – Instanz patchen:

    Diese Lösung ist nicht ideal, da sie Kenntnisse der Interna von Field erfordert, die Sie möglicherweise nicht haben oder verwenden möchten Wenn Sie eine externe Bibliothek verwenden, funktioniert dies jedoch in diesem zugegebenermaßen vereinfachten Fall.,

    Dieses Importzeitproblem ist eines der Hauptprobleme, auf die ich bei der Verwendung von unittest.mockgestoßen bin. Sie müssen sich bei der Verwendung daran erinnern, dass zum Importzeitpunkt Code auf der obersten Ebene der Module ausgeführt wird, einschließlich Klassenkörper.

    Wenn die Logik, die Sie testen, von einer dieser Logik abhängt, müssen Sie möglicherweise überdenken, wie Sie patch entsprechend verwenden.,

    Wrapping up

    Diese Einführung und Cheatsheet sollten Sie mit unittest.mock verspotten, aber ich ermutige Sie, die Dokumentation gründlich zu lesen, es gibt viele weitere interessante Tricks zu lernen.

    Es ist erwähnenswert, dass es alternativen zu unittest.mock, insbesondere Alex gaynors pretend Bibliothek in Kombination mit pytest’s monkeypatch fixture. Wie Alex betont, können Sie mit diesen beiden Bibliotheken einen anderen Ansatz verfolgen, um das Verspotten strenger und dennoch vorhersehbarer zu machen., Auf jeden Fall ein Ansatz, der es wert ist, erkundet zu werden, aber außerhalb des Rahmens dieses Artikels.

    unittest.mock ist derzeit der Standard für das Verspotten in Python und Sie finden es in praktisch jeder Codebasis. Wenn Sie ein solides Verständnis davon haben, können Sie bessere Tests schneller schreiben.

    Articles

    Schreibe einen Kommentar

    Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.