
Hallo habrozhiteli! Mit dem Python-Pfad können Sie Ihre beruflichen Fähigkeiten verbessern und so viel wie möglich über die Funktionen der beliebtesten Programmiersprache lernen. Sie lernen, wie Sie effektiven Code schreiben, die besten Programme in kürzester Zeit erstellen und häufige Fehler vermeiden. Es ist an der Zeit, sich mit Multithreading-Computing und Memoisierung vertraut zu machen, sich von Experten auf dem Gebiet des API- und Datenbankdesigns beraten zu lassen und in Python nachzuschauen, um Ihr Verständnis der Sprache zu erweitern. Sie müssen ein Projekt starten, mit Versionen arbeiten, automatische Tests organisieren und einen Programmierstil für eine bestimmte Aufgabe auswählen. Anschließend studieren Sie effektive Funktionsdeklarationen, wählen geeignete Datenstrukturen und Bibliotheken aus, erstellen störungsfreie Programme, Pakete und optimieren Programme auf Bytecode-Ebene.
Auszug. Tests parallel ausführen
Das Ausführen von Testsuiten kann zeitaufwändig sein. Dies tritt häufig bei großen Projekten auf, wenn die Fertigstellung einer Testsuite Minuten dauert. Standardmäßig führt pytest die Tests nacheinander in einer bestimmten Reihenfolge durch.
Da die meisten Computer über Multi-Core-Prozessoren verfügen, können Sie die Tests beschleunigen, um sie auf mehreren Kernen auszuführen.
Zu diesem Zweck verfügt pytest über ein pytest-xdist-Plugin, das mit pip installiert werden kann. Dieses Plugin erweitert die pytest-Befehlszeile um das Argument ––numprocesses (abgekürzt –n), das die Anzahl der als Argument verwendeten Kerne verwendet. Durch Ausführen von pytest –n 4 wird die Testsuite in vier parallelen Prozessen ausgeführt, wobei ein Gleichgewicht zwischen der Last der verfügbaren Kerne aufrechterhalten wird.
Aufgrund der Tatsache, dass die Anzahl der Kerne variieren kann, akzeptiert das Plugin auch das Schlüsselwort auto als Wert. In diesem Fall wird die Anzahl der verfügbaren Kerne automatisch zurückgegeben.
Erstellen von Objekten, die in Tests mit Fixtures verwendet werden
Bei Komponententests ist es häufig erforderlich, vor und nach dem Ausführen des Tests eine Reihe von Standardvorgängen auszuführen. Diese Anweisungen betreffen bestimmte Komponenten. Beispielsweise benötigen Sie möglicherweise ein Objekt, das den Status der Anwendungskonfiguration ausdrückt. Es muss vor jedem Test initialisiert und nach der Ausführung auf seine ursprünglichen Werte zurückgesetzt werden. Wenn der Test von einer temporären Datei abhängt, muss diese Datei vor dem Test erstellt und danach gelöscht werden. Solche Komponenten werden als
Vorrichtungen bezeichnet . Sie werden vor dem Testen installiert und verschwinden nach ihrer Ausführung.
In pytest werden Vorrichtungen als einfache Funktionen deklariert. Die Fixture-Funktion muss das gewünschte Objekt zurückgeben, damit dieses Objekt beim Testen verwendet werden kann.
Hier ist ein Beispiel für eine einfache Vorrichtung:
import pytest @pytest.fixture def database(): return <some database connection> def test_insert(database): database.insert(123)
Das Datenbank-Fixture wird automatisch von jedem Test verwendet, dessen Liste das Datenbankargument enthält. Die Funktion test_insert () empfängt das Ergebnis der Funktion database () als erstes Argument und verwendet dieses Ergebnis nach eigenem Ermessen. Bei dieser Verwendung von Fixtures müssen Sie den Datenbankinitialisierungscode nicht mehrmals wiederholen.
Ein weiteres gemeinsames Merkmal des Codetests ist die Möglichkeit, überflüssiges Material nach dem Fixture-Vorgang zu entfernen. Schließen Sie beispielsweise die Datenbankverbindung. Durch die Implementierung des Geräts als Generator werden Funktionen zum Reinigen verifizierter Objekte hinzugefügt (Listing 6.5).
Listing 6.5. Ein verifiziertes Objekt löschen
import pytest @pytest.fixture def database(): db = <some database connection> yield db db.close() def test_insert(database): database.insert(123)
Da wir das Yield-Schlüsselwort verwendet und einen Generator aus der Datenbank erstellt haben, wird der Code nach der Yield-Anweisung erst am Ende des Tests ausgeführt. Dieser Code schließt die Datenbankverbindung am Ende des Tests.
Das Schließen der Datenbankverbindung für jeden Test kann zu ungerechtfertigter Verschwendung von Rechenleistung führen, da andere Tests eine bereits offene Verbindung verwenden können. In diesem Fall können Sie das Argument scope an den Fixture Decorator übergeben und dessen Umfang angeben:
import pytest @pytest.fixture(scope="module") def database(): db = <some database connection> yield db db.close() def test_insert(database): database.insert(123)
Durch Angabe des Parameters scope = "module" haben Sie das Gerät einmal für das gesamte Modul initialisiert. Jetzt ist eine offene Datenbankverbindung für alle Testfunktionen verfügbar, die es anfordern.
Sie können vor oder nach dem Test allgemeinen Code ausführen und Fixtures als automatisch mit dem Schlüsselwort autouse verwendet definieren, anstatt sie als Argument für jede Testfunktion anzugeben. Durch die Konkretisierung der Funktion pytest.fixture () mit dem Argument True, dem Schlüsselwort autouse, wird sichergestellt, dass Fixtures jedes Mal aufgerufen werden, bevor der Test in dem Modul oder der Klasse ausgeführt wird, in der er deklariert ist.
import os import pytest @pytest.fixture(autouse=True) def change_user_env(): curuser = os.environ.get("USER") os.environ["USER"] = "foobar" yield os.environ["USER"] = curuser def test_user(): assert os.getenv("USER") == "foobar"</source . , : , , . <h3> </h3> , , , . Gnocchi, . Gnocchi <i>storage API</i>. Python . , API . , ( storage API), , . , <i> </i>, , . 6.6 , : mysql, — postgresql. <blockquote><h4> 6.6. </h4> <source lang="python">import pytest import myapp @pytest.fixture(params=["mysql", "postgresql"]) def database(request): d = myapp.driver(request.param) d.start() yield d d.stop() def test_insert(database): database.insert("somedata")
Das Treiber-Fixture erhält zwei verschiedene Werte als Parameter - die Namen der Datenbanktreiber, die von der Anwendung unterstützt werden. test_insert wird zweimal ausgeführt: einmal für die MySQL-Datenbank und einmal für die PostgreSQL-Datenbank. Dies macht es einfach, denselben Test mit unterschiedlichen Szenarien erneut durchzuführen, ohne neue Codezeilen hinzuzufügen.
Verwaltete Tests mit Dummy-Objekten
Dummy-Objekte (oder Stubs, Scheinobjekte) sind Objekte, die das Verhalten realer Anwendungsobjekte nachahmen, sich jedoch in einem speziellen, kontrollierten Zustand befinden. Sie sind am nützlichsten beim Erstellen von Umgebungen, in denen die Bedingungen für den Test ausführlich beschrieben werden. Sie können alle Objekte außer dem getesteten durch Dummy-Objekte ersetzen und isolieren sowie eine Umgebung für Codetests erstellen.
Ein Anwendungsfall ist das Erstellen eines HTTP-Clients. Es ist fast unmöglich (oder vielmehr unglaublich schwierig), einen HTTP-Server zu erstellen, auf dem Sie alle Situationen und Szenarien für jeden möglichen Wert ausführen können. HTTP-Clients sind besonders schwer auf Fehlerszenarien zu testen.
Die Standardbibliothek verfügt über einen Mock-Befehl zum Erstellen eines Dummy-Objekts. Ab Python 3.3 wurde mock in die Bibliothek unittest.mock integriert. Daher können Sie das folgende Codefragment verwenden, um die Abwärtskompatibilität zwischen Python 3.3 und früheren Versionen zu gewährleisten:
try: from unittest import mock except ImportError: import mock
Die Scheinbibliothek ist sehr einfach zu bedienen. Jedes für das mock.Mock-Objekt verfügbare Attribut wird zur Laufzeit dynamisch erstellt. Jedem Attribut kann ein beliebiger Wert zugewiesen werden. In Listing 6.7 wird mock verwendet, um ein Dummy-Objekt für das Dummy-Attribut zu erstellen.
Listing 6.7. Zugriff auf das Attribut mock.Mock
>>> from unittest import mock >>> m = mock.Mock() >>> m.some_attribute = "hello world" >>> m.some_attribute "hello world"
Sie können auch dynamisch eine Methode für ein veränderbares Objekt erstellen, wie in Listing 6.8, wo Sie eine Dummy-Methode erstellen, die immer 42 zurückgibt und alles, was Sie wollen, als Argument verwendet.
Listing 6.8. Erstellen einer Methode für das mock.Mock-Dummy-Objekt
>>> from unittest import mock >>> m = mock.Mock() >>> m.some_method.return_value = 42 >>> m.some_method() 42 >>> m.some_method("with", "arguments") 42
Nur ein paar Zeilen, und das mock.Mock-Objekt verfügt jetzt über eine some_method () -Methode, die 42 zurückgibt. Es wird jede Art von Argument verwendet, während keine Überprüfung des Arguments erfolgt.
Dynamisch generierte Methoden können auch (absichtliche) Nebenwirkungen haben. Um nicht nur Boilerplate-Methoden zu sein, die einen Wert zurückgeben, können sie definiert werden, um nützlichen Code auszuführen.
Listing 6.9 erstellt eine Dummy-Methode, die einen Nebeneffekt hat - sie zeigt die Zeichenfolge „Hallo Welt“ an.
Listing 6.9. Erstellen einer Methode für ein mock.Mock-Objekt mit Nebeneffekt
>>> from unittest import mock >>> m = mock.Mock() >>> def print_hello(): ... print("hello world!") ... return 43 ... ❶ >>> m.some_method.side_effect = print_hello >>> m.some_method() hello world! 43 ❷ >>> m.some_method.call_count 1
Wir haben dem Attribut some_method ❶ eine ganze Funktion zugewiesen. Technisch gesehen können Sie so ein komplexeres Szenario in den Test implementieren, da Sie den für den Test erforderlichen Code in das Dummy-Objekt aufnehmen können. Als Nächstes müssen Sie dieses Objekt an die Funktion übergeben, die es erwartet.
Mit dem Attribut ❷ call_count können Sie auf einfache Weise überprüfen, wie oft eine Methode aufgerufen wurde.
Die Scheinbibliothek verwendet das Muster "Aktionsprüfung": Dies bedeutet, dass Sie nach dem Testen sicherstellen müssen, dass die durch Dummies ersetzten Aktionen korrekt ausgeführt wurden. Listing 6.10 wendet die assert () -Methode auf Dummy-Objekte an, um diese Überprüfungen durchzuführen.
Listing 6.10. Methoden zur Anrufüberprüfung
>>> from unittest import mock >>> m = mock.Mock() ❶ >>> m.some_method('foo', 'bar') <Mock name='mock.some_method()' id='26144272'> ❷ >>> m.some_method.assert_called_once_with('foo', 'bar') >>> m.some_method.assert_called_once_with('foo', ❸mock.ANY) >>> m.some_method.assert_called_once_with('foo', 'baz') Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/lib/python2.7/dist-packages/mock.py", line 846, in assert_cal led_once_with return self.assert_called_with(*args, **kwargs) File "/usr/lib/python2.7/dist-packages/mock.py", line 835, in assert_cal led_with raise AssertionError(msg) AssertionError: Expected call: some_method('foo', 'baz') Actual call: some_method('foo', 'bar')
Wir haben Methoden mit foo- und bar-Argumenten als Tests erstellt, indem wir die ❶-Methode aufgerufen haben. Eine einfache Möglichkeit, Aufrufe von Dummy-Objekten zu testen, besteht darin, assert_called () -Methoden zu verwenden, z. B. assert_called_once_with () ❷. Für diese Methoden müssen Sie die Werte übergeben, die beim Aufruf der Dummy-Methode erwartet werden. Wenn sich die übergebenen Werte von den verwendeten unterscheiden, löst mock eine AssertionError-Ausnahme aus. Wenn Sie nicht wissen, welche Argumente übergeben werden können, verwenden Sie mock.ANY als Wert von ❸; Es ersetzt alle Argumente, die an die Dummy-Methode übergeben wurden.
Die Scheinbibliothek kann auch verwendet werden, um eine Funktion, Methode oder ein Objekt aus einem externen Modul zu ersetzen. In Listing 6.11 haben wir die Funktion os.unlink () durch unsere eigene Dummy-Funktion ersetzt.
Listing 6.11. Mock.patch verwenden
>>> from unittest import mock >>> import os >>> def fake_os_unlink(path): ... raise IOError("Testing!") ... >>> with mock.patch('os.unlink', fake_os_unlink): ... os.unlink('foobar') ... Traceback (most recent call last): File "<stdin>", line 2, in <module> File "<stdin>", line 2, in fake_os_unlink IOError: Testing!
Bei Verwendung als Kontextmanager ersetzt mock.patch () die Zielfunktion durch die von uns ausgewählte. Dies ist erforderlich, damit der im Kontext ausgeführte Code die korrigierte Methode verwendet. Mit der Methode mock.patch () können Sie einen beliebigen Teil des externen Codes ändern und ihn dazu zwingen, sich so zu verhalten, dass alle Bedingungen für die Anwendung getestet werden (Listing 6.12).
Listing 6.12. Verwenden von mock.patch () zum Testen vieler Verhaltensweisen
from unittest import mock import pytest import requests class WhereIsPythonError(Exception): pass ❶ def is_python_still_a_programming_language(): try: r = requests.get("http://python.org") except IOError: pass else: if r.status_code == 200: return 'Python is a programming language' in r.content raise WhereIsPythonError("Something bad happened") def get_fake_get(status_code, content): m = mock.Mock() m.status_code = status_code m.content = content def fake_get(url): return m return fake_get def raise_get(url): raise IOError("Unable to fetch url %s" % url) ❷ @mock.patch('requests.get', get_fake_get( 200, 'Python is a programming language for sure')) def test_python_is(): assert is_python_still_a_programming_language() is True @mock.patch('requests.get', get_fake_get( 200, 'Python is no more a programming language')) def test_python_is_not(): assert is_python_still_a_programming_language() is False @mock.patch('requests.get', get_fake_get(404, 'Whatever')) def test_bad_status_code(): with pytest.raises(WhereIsPythonError): is_python_still_a_programming_language() @mock.patch('requests.get', raise_get) def test_ioerror(): with pytest.raises(WhereIsPythonError): is_python_still_a_programming_language()
Listing 6.12 implementiert einen Testfall, der nach allen Instanzen von
Python sucht. Es
handelt sich um eine Programmiersprachenzeichenfolge auf
python.org ❶. Es gibt keine Option, bei der der Test keine bestimmte Zeile auf der ausgewählten Webseite findet. Um ein negatives Ergebnis zu erhalten, müssen Sie die Seite ändern, dies ist jedoch nicht möglich. Mit Hilfe von mock können Sie jedoch zum Trick gehen und das Verhalten der Anforderung so ändern, dass eine Dummy-Antwort mit einer fiktiven Seite zurückgegeben wird, die keine bestimmte Zeichenfolge enthält. Auf diese Weise können Sie ein negatives Szenario testen, in dem
python.org keine bestimmte Zeichenfolge enthält, und sicherstellen, dass das Programm einen solchen Fall korrekt behandelt.
In diesem Beispiel wird die Version mock.patch () decorator verwendet. Das Verhalten des Dummy-Objekts ändert sich nicht und es war einfacher, ein Beispiel im Kontext einer Testfunktion zu setzen.
Die Verwendung eines Dummy-Objekts hilft bei der Simulation eines Problems: Der Server gibt einen 404-Fehler, einen E / A-Fehler oder einen Netzwerkverzögerungsfehler zurück. Wir können sicherstellen, dass der Code die richtigen Werte zurückgibt oder jeweils die richtige Ausnahme auslöst, was das erwartete Verhalten des Codes garantiert.
Identifizieren Sie nicht getesteten Code mit Abdeckung
Eine großartige Ergänzung zum Unit-Test ist das Coverage-Tool. [Die Code-Coverage ist ein Maß für das Testen. Zeigt den Prozentsatz des Quellcodes des Programms an, der während des Testprozesses ausgeführt wurde
. ], die nicht getestete Codeteile findet. Es verwendet Tools zur Codeanalyse und -verfolgung, um die ausgeführten Zeilen zu identifizieren. Beim Unit-Test kann festgestellt werden, welche Teile des Codes wiederverwendet und welche überhaupt nicht verwendet wurden. Das Erstellen von Tests ist erforderlich, und die Möglichkeit, herauszufinden, welchen Teil des Codes Sie vergessen haben, mit Tests zu behandeln, macht diesen Prozess angenehmer.
Installieren Sie das Abdeckungsmodul über Pip, um es über Ihre Shell verwenden zu können.
HINWEIS
Der Befehl kann auch als Python-Coverage bezeichnet werden, wenn das Modul über das Installationsprogramm Ihres Betriebssystems installiert wird. Ein Beispiel hierfür ist das Debian-Betriebssystem.
Die Offline-Verwendung der Abdeckung ist ziemlich einfach. Es werden die Teile des Programms angezeigt, die niemals gestartet werden und zu einem "Eigengewicht" werden - ein Code, den Sie nicht entfernen können, ohne die Funktionalität des Programms zu ändern. Alle Testwerkzeuge, die weiter oben in diesem Kapitel erläutert wurden, sind in die Abdeckung integriert.
Wenn Sie pytest verwenden, installieren Sie das Plugin pytest-cov über pip install pytest-pycov und fügen Sie einige Schalter hinzu, um eine detaillierte Ausgabe des nicht getesteten Codes zu generieren (Listing 6.13).
Listing 6.13. Mit Pytest und Abdeckung
$ pytest --cov=gnocchiclient gnocchiclient/tests/unit ---------- coverage: platform darwin, python 3.6.4-final-0 ----------- Name Stmts Miss Branch BrPart Cover --------------------------- gnocchiclient/__init__.py 0 0 0 0 100% gnocchiclient/auth.py 51 23 6 0 49% gnocchiclient/benchmark.py 175 175 36 0 0% --snip-- --------------------------- TOTAL 2040 1868 424 6 8% === passed in 5.00 seconds ===
Die Option --cov aktiviert die Ausgabe des Abdeckungsberichts am Ende des Tests. Sie müssen den Paketnamen als Argument übergeben, damit das Plugin den Bericht ordnungsgemäß filtert. Die Ausgabe enthält Codezeilen, die nicht ausgeführt wurden, dh sie wurden nicht getestet. Sie müssen lediglich den Editor öffnen und einen Test für diesen Code schreiben.
Das Abdeckungsmodul ist noch besser - es ermöglicht Ihnen, klare Berichte im HTML-Format zu erstellen. Fügen Sie einfach -–cov-report-html hinzu, und HTML-Seiten werden im Verzeichnis
htmlcov angezeigt, von dem aus Sie den Befehl ausführen. Auf jeder Seite wird angezeigt, welche Teile des Quellcodes ausgeführt wurden oder nicht.
Wenn Sie noch weiter gehen möchten, verwenden Sie –-cover-fail-under-COVER_MIN_PERCENTAGE. Dies führt dazu, dass die Testsuite fehlschlägt, wenn sie nicht den Mindestprozentsatz an Code abdeckt. Obwohl ein großer Prozentsatz der Abdeckung ein gutes Ziel ist und Testwerkzeuge nützlich sind, um Informationen über den Status der Testabdeckung zu erhalten, ist der Prozentsatz an sich nicht sehr informativ. Abbildung 6.1 zeigt einen beispielhaften Abdeckungsbericht mit einer prozentualen Abdeckung.
Zum Beispiel ist es ein würdiges Ziel, den Code zu 100% mit Tests zu bedecken. Dies bedeutet jedoch nicht unbedingt, dass der Code vollständig getestet wurde. Dieser Wert zeigt nur an, dass alle Codezeilen im Programm erfüllt sind, zeigt jedoch nicht an, dass alle Bedingungen getestet wurden.
Es lohnt sich, Abdeckungsinformationen zu verwenden, um die Testsuite zu erweitern und sie für Code zu erstellen, der nicht ausgeführt wird. Dies vereinfacht die Projektunterstützung und verbessert die allgemeine Codequalität.
Über den Autor
Julien Danju hackt seit ungefähr zwanzig Jahren Freeware und entwickelt seit fast zwölf Jahren Python-Programme. Derzeit leitet er das Designteam für die OpenStack-basierte verteilte Cloud-Plattform, die die größte vorhandene Python-Open-Source-Datenbank mit etwa zweieinhalb Millionen Codezeilen besitzt. Vor der Entwicklung von Cloud-Diensten erstellte Julien den Fenstermanager und trug zur Entwicklung vieler Projekte bei, darunter Debian und GNU Emacs.
Über Science Editor
Mike Driscoll programmiert seit über einem Jahrzehnt in Python. Lange schrieb er über Python in
The Mouse vs. Der Python . Autor mehrerer Python-Bücher: Python 101, Python-Interviews und ReportLab: PDF-Verarbeitung mit Python. Sie finden Mike auf Twitter und auf GitHub: @driscollis.
»Weitere Informationen zum Buch finden Sie auf
der Website des Herausgebers»
Inhalt»
Auszug25% Rabatt auf Gutschein für Händler -
PythonNach Bezahlung der Papierversion des Buches wird ein elektronisches Buch per E-Mail verschickt.