Schreiben Sie professionelle Komponententests in Python

Testen ist die Grundlage für eine solide Softwareentwicklung. Es gibt viele Arten von Tests, aber der wichtigste ist das Testen von Einheiten. Unit-Tests geben Ihnen die Gewissheit, dass Sie bewährte Teile als Grundelemente verwenden und sich beim Erstellen des Programms auf sie verlassen können. Sie erhöhen Ihren Bestand an vertrauenswürdigem Code über die eingebauten und Standardbibliotheken Ihrer Sprache hinaus. Darüber hinaus bietet Python eine hervorragende Unterstützung für das Schreiben von Komponententests.

Beispiel ausführen

Bevor wir in alle Prinzipien, Heuristiken und Richtlinien eintauchen, sehen wir einen repräsentativen Unit-Test in Aktion. Das SelfDrivingCar Klasse ist eine teilweise Implementierung der Fahrlogik eines selbstfahrenden Autos. Es befasst sich hauptsächlich mit der Steuerung der Geschwindigkeit des Autos. Es erkennt Objekte vor ihm, die Geschwindigkeitsbegrenzung und ob es am Ziel angekommen ist oder nicht. 

Klasse SelfDrivingCar (object): def __init __ (self): self.speed = 0 self.destination = keine def _accelerate (self): self.speed + = 1 def _decelerate (self): wenn self.speed> 0: self.speed - = 1 def _advance_to_destination (self): distance = self._calculate_distance_to_object_in_front () wenn distance < 10: self.stop() elif distance < self.speed / 2: self._decelerate() elif self.speed < self._get_speed_limit(): self._accelerate() def _has_arrived(self): pass def _calculate_distance_to_object_in_front(self): pass def _get_speed_limit(self): pass def stop(self): self.speed = 0 def drive(self, destination): self.destination = destination while not self._has_arrived(): self._advance_to_destination() self.stop() def __init__(self): self.speed = 0 self.destination = None def _accelerate(self): self.speed += 1 def _decelerate(self): if self.speed > 0: self.speed - = 1 def _advance_to_destination (self): distance = self._calculate_distance_to_object_in_front () wenn distance < 10: self.stop() elif distance < self.speed / 2: self._decelerate() elif self.speed < self._get_speed_limit(): self._accelerate() def _has_arrived(self): pass def _calculate_distance_to_object_in_front(self): pass def _get_speed_limit(self): pass def stop(self): self.speed = 0 def drive(self, destination): self.destination = destination while not self._has_arrived(): self._advance_to_destination() self.stop() 

Hier ist ein Unit-Test für die halt() Methode, um Ihren Appetit anzuregen. Ich werde später auf die Details eingehen. 

von unittest import TestCase-Klasse SelfDrivingCarTest (TestCase): def setUp (self): self.car = SelfDrivingCar () def test_stop (self): self.car.speed = 5 self.car.stop () # Vergewissern Sie sich, dass die Geschwindigkeit 0 ist self.assertEqual stoppen (0, self.car.speed) # Vergewissern Sie sich, dass das Anhalten wieder in Ordnung ist, wenn das Fahrzeug bereits angehalten ist. self.car.stop () self.assertEqual (0, self.car.speed)

Richtlinien zur Prüfung von Einheiten

Verpflichten

Gute Unit-Tests zu schreiben ist harte Arbeit. Das Schreiben von Komponententests erfordert Zeit. Wenn Sie Änderungen an Ihrem Code vornehmen, müssen Sie normalerweise auch Ihre Tests ändern. Manchmal haben Sie Fehler in Ihrem Testcode. Das bedeutet, dass Sie wirklich engagiert sein müssen. Die Vorteile sind enorm, auch für kleine Projekte, aber sie sind nicht kostenlos.

Sei diszipliniert

Sie müssen diszipliniert sein. Seien Sie konsequent. Stellen Sie sicher, dass die Tests immer bestanden werden. Lassen Sie die Tests nicht kaputt gehen, weil Sie "wissen", dass der Code in Ordnung ist.

Automatisieren

Um Ihnen zu helfen, diszipliniert zu sein, sollten Sie Ihre Komponententests automatisieren. Die Tests sollten automatisch an wichtigen Punkten wie vor dem Festschreiben oder vor der Bereitstellung ausgeführt werden. Idealerweise weist Ihr Quellcodeverwaltungssystem Code zurück, der nicht alle Tests bestanden hat.

Nicht getesteter Code ist per Definition defekt

Wenn Sie es nicht getestet haben, können Sie nicht sagen, dass es funktioniert. Dies bedeutet, dass Sie es für gebrochen halten sollten. Wenn es sich um kritischen Code handelt, stellen Sie ihn nicht für die Produktion bereit.

Hintergrund

Was ist eine Einheit??

Eine Einheit zum Testen von Einheiten ist eine Datei / ein Modul, die einen Satz verwandter Funktionen oder eine Klasse enthält. Wenn Sie eine Datei mit mehreren Klassen haben, sollten Sie für jede Klasse einen Komponententest schreiben.

Zu TDD oder nicht zu TDD

Testgetriebene Entwicklung ist eine Praxis, bei der Sie die Tests schreiben, bevor Sie den Code schreiben. Dieser Ansatz hat mehrere Vorteile, aber ich empfehle es zu vermeiden, wenn Sie die Disziplin haben, später geeignete Tests zu schreiben. 

Der Grund ist, dass ich mit Code entwerfe. Ich schreibe Code, schaue ihn an, schreibe ihn neu, schaue ihn noch einmal an und schreibe ihn sehr schnell wieder. Das Schreiben von Tests begrenzt mich zunächst und bremst mich ab. 

Sobald ich mit dem anfänglichen Design fertig bin, schreibe ich die Tests sofort, bevor ich sie in den Rest des Systems integrieren kann. Das heißt, es ist eine großartige Möglichkeit, sich mit Komponententests vertraut zu machen, und stellt sicher, dass der gesamte Code über Tests verfügt.

Das einzigste Modul

Das einzigste Modul wird mit der Standardbibliothek von Python geliefert. Es bietet eine Klasse namens Testfall, von dem Sie Ihre Klasse ableiten können. Dann kannst du eine überschreiben Konfiguration() Methode zur Vorbereitung eines Testgeräts vor jedem Test und / oder a classSetUp () Klassenmethode zum Vorbereiten eines Testgeräts für alle Tests (nicht zwischen einzelnen Tests zurückgesetzt). Es gibt entsprechende niederreissen() und classTearDown () Methoden, die Sie auch überschreiben können.

Hier sind die relevanten Teile aus unserem SelfDrivingCarTest Klasse. Ich benutze nur das Konfiguration() Methode. Ich erstelle ein frisches SelfDrivingCar Instanz und speichern Sie es in selbst.auto es ist also für jeden Test verfügbar.

von unittest import TestCase-Klasse SelfDrivingCarTest (TestCase): def setUp (self): self.car = SelfDrivingCar ()

Der nächste Schritt ist, spezifische Testmethoden zu schreiben, um diesen Code unter Test-the zu testen SelfDrivingCar Klasse in diesem Fall-tut, was es tun soll. Der Aufbau einer Testmethode ist ziemlich normal:

  • Umgebung vorbereiten (optional).
  • Bereiten Sie das erwartete Ergebnis vor.
  • Rufen Sie den zu testenden Code an.
  • Stellen Sie sicher, dass das tatsächliche Ergebnis mit dem erwarteten Ergebnis übereinstimmt.

Beachten Sie, dass das Ergebnis nicht die Ausgabe einer Methode sein muss. Dies kann eine Statusänderung einer Klasse sein, ein Nebeneffekt, wie das Hinzufügen einer neuen Zeile in einer Datenbank, das Schreiben einer Datei oder das Senden einer E-Mail.

Zum Beispiel die halt() Methode der SelfDrivingCar Klasse gibt nichts zurück, ändert jedoch den internen Zustand, indem sie die Geschwindigkeit auf 0 setzt assertEqual () Methode bereitgestellt von der Testfall Die Basisklasse wird hier verwendet, um diesen Aufruf zu überprüfen halt() funktionierte wie erwartet.

def test_stop (self): self.car.speed = 5 self.car.stop () # Vergewissern Sie sich, dass die Geschwindigkeit 0 ist, nachdem Sie self.assertEqual (0, self.car.speed) gestoppt haben Auto ist bereits angehalten self.car.stop () self.assertEqual (0, self.car.speed)

Hier gibt es eigentlich zwei Tests. Der erste Test besteht darin, sicherzustellen, dass bei einer Geschwindigkeit von 5 und halt() aufgerufen wird, dann wird die Geschwindigkeit 0. Dann ist ein weiterer Test, um sicherzustellen, dass beim Aufruf nichts schief geht halt() wieder, wenn das Auto bereits gestoppt ist.

Später werde ich noch einige Tests für zusätzliche Funktionen einführen.

Das Doctest-Modul

Das Doctest-Modul ist ziemlich interessant. Sie können damit interaktive Codebeispiele in Ihrem Docstring verwenden und die Ergebnisse überprüfen, einschließlich der erhöhten Ausnahmen. 

Ich benutze oder empfehle doctest nicht für große Systeme. Richtiges Testen von Einheiten erfordert viel Arbeit. Der Testcode ist normalerweise viel größer als der zu testende Code. Docstrings sind einfach nicht das richtige Medium, um umfangreiche Tests zu schreiben. Sie sind aber cool. Hier ist was ein Fakultät Funktion mit Doc-Tests sieht wie folgt aus:

import math def factorial (n): "" "Gibt die Fakultät von n zurück, eine genaue ganze Zahl> = 0. Wenn das Ergebnis klein genug ist, um in ein int zu passen, geben Sie ein int zurück. Andernfalls geben Sie einen langen Wert zurück. >>> [Fakultät (n) für n im Bereich (6)] [1, 1, 2, 6, 24, 120] >>> [Fakultät (lang (n)) für n im Bereich (6)] [1, 1, 2, 6, 24, 120] >>> factorial (30) 265252859812191058636308480000000L >>> factorial (30L) 265252859812191058636308480000000L >>> factorial (-1) Traceback (letzter Aufruf zuletzt):… ValueError: n muss> 0 Factorials von Floats sein sind in Ordnung, aber der Float muss eine genaue Ganzzahl sein: >>> Fakultät (30.1) Traceback (letzter Aufruf zuletzt):… ValueError: n muss eine Ganzzahl sein >>> Fakultät (30.0) 265252859812191058636308480000000L Es darf auch nicht lächerlich groß sein : >>> factorial (1e100) Traceback (letzter Aufruf zuletzt):… OverflowError: n zu groß "" "wenn nicht n> = 0: Erhöhung ValueError (" n muss> = 0 "), wenn math.floor (n )! = n: raise ValueError ("n muss eine exakte Ganzzahl sein"), wenn n + 1 == n: # einen Wert wie 1e300 Raise overflowE fangen rror ("n zu groß") Ergebnis = 1 Faktor = 2 während Faktor <= n: result *= factor factor += 1 return result if __name__ == "__main__": import doctest doctest.testmod()

Wie Sie sehen können, ist der Docstring viel größer als der Funktionscode. Es fördert nicht die Lesbarkeit.

Tests ausführen

OK. Sie haben Ihre Unit-Tests geschrieben. Bei einem großen System haben Sie zehntausende / tausende von Modulen und Klassen in möglicherweise mehreren Verzeichnissen. Wie führen Sie all diese Tests durch??

Das unittest-Modul bietet verschiedene Möglichkeiten, um Tests zu gruppieren und programmgesteuert auszuführen. Testen Sie das Laden und Ausführen von Tests. Der einfachste Weg ist jedoch die Entdeckung von Tests. Diese Option wurde nur in Python 2.7 hinzugefügt. Vor Version 2.7 konnten Sie die Nase zum Erkennen und Ausführen von Tests verwenden. Nase hat einige andere Vorteile, wie das Ausführen von Testfunktionen, ohne eine Klasse für Ihre Testfälle erstellen zu müssen. Aber für den Zweck dieses Artikels, lasst uns bei allem bleiben.

Geben Sie einfach in die Befehlszeile Folgendes ein, um Ihre Tests zu ermitteln, die auf Tests basieren

Python -m einsamsten entdecken

unittest prüft alle Dateien und Unterverzeichnisse, führt alle gefundenen Tests durch und liefert einen schönen Bericht sowie eine Laufzeit. Wenn Sie sehen möchten, welche Tests ausgeführt werden, können Sie das Flag -v hinzufügen:

Python -m einzige Entdeckung -v

Es gibt mehrere Flags, die den Vorgang steuern:

python -m unittest -h Verwendung: python -m unittest [Optionen] [Tests] Optionen: -h, --help Diese Nachricht anzeigen -v, --verbose Verbose Ausgabe -q, --quiet Minimale Ausgabe -f, - failfast Beim ersten Fehler stoppen -c, --catch Catch abfangen und Ergebnis anzeigen -b, --buffer Puffer stdout und stderr während Testdurchläufen Beispiele: python -m unittest test_module - testet das Testmodul python -m unittest module.TestClass - Tests von module.TestClass ausführen python -m unittest module.Class.test_method - angegebene Testmethode ausführen [tests] kann eine Liste einer beliebigen Anzahl von Testmodulen, Klassen und Testmethoden sein. Alternative Verwendung: python -m unittest discover [Optionen] Optionen: -v, --verbose Verbose Ausgabe -f, --failfast Stoppen beim ersten Fehler -c, --catch Catch-Steuerung-C und Anzeige der Ergebnisse -b, --buffer Puffer stdout und stderr während Testdurchläufen -s Verzeichnis Verzeichnis zum Starten der Erkennung (Standardeinstellung '.') -P Muster Muster für Testdateien (Standardeinstellung 'test * .py') -t Verzeichnis Verzeichnis der obersten Ebene des Projekts (standardmäßig Startverzeichnis) ) Zur Testerkennung müssen alle Testmodule aus dem Verzeichnis der obersten Ebene des Projekts importierbar sein.

Testabdeckung

Testabdeckung ist ein oft vernachlässigtes Feld. Abdeckung bedeutet, wie viel von Ihrem Code tatsächlich von Ihren Tests getestet wird. Zum Beispiel, wenn Sie eine Funktion mit einer ansonsten Aussage und Sie testen nur die ob verzweigen, dann wissen Sie nicht, ob die sonst Zweig arbeitet oder nicht. Im folgenden Codebeispiel die Funktion hinzufügen() prüft die Art seiner Argumente. Wenn beide Ganzzahlen sind, werden sie einfach hinzugefügt. 

Wenn beide Zeichenfolgen sind, wird versucht, sie in Ganzzahlen umzuwandeln und sie hinzuzufügen. Andernfalls wird eine Ausnahme ausgelöst. Das test_add () Funktion testet das hinzufügen() funktionieren mit Argumenten, die sowohl Ganzzahlen sind, als auch mit Argumenten, die Floats sind, und überprüfen jeweils das korrekte Verhalten. Die Testabdeckung ist jedoch unvollständig. Der Fall von String-Argumenten wurde nicht getestet. Infolgedessen wird der Test erfolgreich bestanden, aber der Tippfehler in der Verzweigung, in der die Argumente beide Zeichenfolgen sind, wurde nicht gefunden (siehe 'intg' dort?)..

import unittest def add (a, b): "" "Diese Funktion fügt zwei Zahlen a, b hinzu und gibt ihre Summe a und b möglicherweise ganze Zahlen" "" zurück, wenn instanz (a, int) und instanz (b, int): a angeben + b elseif isinstance (a, str) und isinstance (b, str): return int (a) + intg (b) else: Rais Exception ('Invalid arguments') Klasse Test (unittest.TestCase): def test_add (self) : self.assertEqual (5, add (2, 3)) self.assertEqual (15, add (-6, 21)) self.assertRaises (Ausnahme add, 4.0, 5.0) unittest.main () 

Hier ist die Ausgabe:

---------------------------------------------------------------------- Test 1 in 0.000 Sekunden ausgeführt OK Der Vorgang wurde mit dem Beendigungscode 0 abgeschlossen

Hands-On-Unit-Tests

Das Schreiben von Unit-Unit-Tests mit industrieller Stärke ist nicht einfach oder einfach. Es gibt mehrere Dinge, die zu berücksichtigen sind und Kompromisse gemacht werden müssen.

Design für Testbarkeit

Wenn es sich bei Ihrem Code um einen so genannten Spaghetti-Code oder um einen großen Schlammballen handelt, bei dem verschiedene Abstraktionsebenen miteinander vermischt werden und jeder Code von jedem anderen Code abhängt, fällt es Ihnen schwer, ihn zu testen. Immer wenn Sie etwas ändern, müssen Sie auch eine Reihe von Tests aktualisieren.

Die gute Nachricht ist, dass das richtige Softwaredesign für allgemeine Zwecke genau das ist, was Sie für die Testbarkeit benötigen. Insbesondere der gut sortierte modulare Code, bei dem jede Komponente eine klare Verantwortung trägt und über gut definierte Schnittstellen mit anderen Komponenten interagiert, macht das Schreiben guter Unit-Tests zum Vergnügen.

Zum Beispiel unser SelfDrivingCar Die Klasse ist für die übergeordnete Bedienung des Autos verantwortlich: Gehen, Anhalten, Navigieren. Es hat ein berechnen_abstand_zu_objekt_in_front () Methode, die noch nicht implementiert wurde. Diese Funktionalität sollte wahrscheinlich von einem völlig separaten Subsystem implementiert werden. Es kann das Lesen von Daten von verschiedenen Sensoren, das Zusammenwirken mit anderen selbstfahrenden Autos und einen gesamten Machine Vision Stack umfassen, um Bilder von mehreren Kameras zu analysieren.

Mal sehen, wie das in der Praxis funktioniert. Das SelfDrivingCar akzeptiert ein Argument namens object_detector das hat eine Methode aufgerufen berechnen_abstand_zu_objekt_in_front (), und diese Funktion wird an dieses Objekt delegiert. Jetzt ist es nicht notwendig, dies zu testen, da die object_detector ist dafür verantwortlich (und sollte getestet werden). Sie möchten immer noch testen, ob Sie das verwenden object_detector richtig.

Klasse SelfDrivingCar (object): def __init __ (self, object_detector): self.object_detector self.speed = 0 self.destination = Keine def _calculate_distance_to_object_in_front (self): return self.object_detector.calculate_distance_to_object_in_front ()

Kosten-Nutzen

Der Aufwand, den Sie für das Testen aufwenden, sollte mit den Ausfallkosten, der Stabilität des Codes und der Behebung von Problemen in Verbindung gebracht werden, wenn Probleme auf der ganzen Linie entdeckt werden.

Zum Beispiel ist unsere selbstfahrende Autoklasse äußerst kritisch. Wenn die halt() Methode funktioniert nicht richtig, unser selbstfahrendes Auto könnte Menschen töten, Eigentum zerstören und den gesamten Markt für selbstfahrende Automobile entgleisen. Wenn Sie ein selbstfahrendes Auto entwickeln, vermute ich, dass Ihre Unit-Tests für den halt() Methode wird etwas strenger sein als meine. 

Wenn dagegen eine einzelne Schaltfläche in Ihrer Webanwendung auf einer Seite, die drei Ebenen unter Ihrer Hauptseite eingebettet ist, ein wenig flackert, wenn jemand darauf klickt, können Sie dies beheben, aber es wird wahrscheinlich kein dedizierter Komponententest für diesen Fall hinzugefügt. Die Wirtschaft rechtfertigt es einfach nicht. 

Mindset testen

Die Denkweise zu testen ist wichtig. Ein Prinzip, das ich verwende, ist, dass jeder Code mindestens zwei Benutzer hat: den anderen Code, der ihn verwendet, und den Test, der ihn testet. Diese einfache Regel hilft sehr beim Design und bei Abhängigkeiten. Wenn Sie sich daran erinnern, dass Sie einen Test für Ihren Code schreiben müssen, fügen Sie nicht viele Abhängigkeiten hinzu, die beim Testen schwer zu rekonstruieren sind.

Angenommen, Ihr Code muss etwas berechnen. Zu diesem Zweck muss er einige Daten aus einer Datenbank laden, eine Konfigurationsdatei lesen und einige aktuelle REST-APIs dynamisch abfragen. Dies alles kann aus verschiedenen Gründen erforderlich sein, aber wenn Sie dies alles in einer einzigen Funktion zusammenfassen, wird es ziemlich schwierig, den Einheitentest durchzuführen. Es ist immer noch möglich mit Spott, aber es ist viel besser, Ihren Code richtig zu strukturieren.

Reine Funktionen

Der am einfachsten zu testende Code ist reine Funktionen. Reine Funktionen sind Funktionen, die nur auf die Werte ihrer Parameter zugreifen, keine Nebenwirkungen haben und das gleiche Ergebnis zurückgeben, wenn sie mit denselben Argumenten aufgerufen werden. Sie ändern nicht den Status Ihres Programms, greifen nicht auf das Dateisystem oder das Netzwerk zu. Ihre Vorteile sind zu viel, um hier zu zählen. 

Warum sind sie leicht zu testen? Weil es nicht notwendig ist, eine spezielle Umgebung für den Test festzulegen. Sie übergeben einfach Argumente und testen das Ergebnis. Sie wissen auch, dass sich der Test nicht ändern muss, solange sich der getestete Code nicht ändert. 

Vergleichen Sie es mit einer Funktion, die eine XML-Konfigurationsdatei liest. Ihr Test muss eine XML-Datei erstellen und den Dateinamen an den zu testenden Code übergeben. Keine große Sache. Angenommen, jemand hat entschieden, dass XML abscheulich ist und alle Konfigurationsdateien in JSON sein müssen. Sie gehen ihrem Geschäft nach und konvertieren alle Konfigurationsdateien in JSON. Sie führen alle Tests einschließlich Ihrer Tests und sie durch alles bestehen! 

Warum? Weil sich der Code nicht geändert hat. Es wird weiterhin eine XML-Konfigurationsdatei erwartet, und Ihr Test erstellt immer noch eine XML-Datei dafür. In der Produktion erhält Ihr Code jedoch eine JSON-Datei, die nicht analysiert werden kann.

Fehlerbehandlung testen

Fehlerbehandlung ist eine andere Sache, die zum Testen kritisch ist. Es ist auch Teil des Designs. Wer ist für die Richtigkeit der Eingabe verantwortlich? Jede Funktion und Methode sollte klar sein. Wenn es die Verantwortung der Funktion ist, sollte sie ihre Eingabe überprüfen, aber wenn es die Verantwortung des Anrufers ist, kann die Funktion einfach ihre Arbeit erledigen und davon ausgehen, dass die Eingabe korrekt ist. Die allgemeine Korrektheit des Systems wird sichergestellt, indem Tests für den Anrufer durchgeführt werden, um sicherzustellen, dass nur korrekte Eingaben an Ihre Funktion übergeben werden.

Normalerweise möchten Sie die Eingabe der öffentlichen Schnittstelle mit Ihrem Code überprüfen, da Sie nicht unbedingt wissen, wer Ihren Code anruft. Schauen wir uns das an Fahrt() Methode des selbstfahrenden Autos. Diese Methode erwartet einen 'Ziel'-Parameter. Der Parameter 'destination' wird später in der Navigation verwendet. Die Laufwerksmethode überprüft jedoch nicht, ob sie korrekt ist. 

Nehmen wir an, dass das Ziel ein Tupel von Längen- und Breitengrad sein soll. Es gibt alle Arten von Tests, die durchgeführt werden können, um zu bestätigen, dass sie gültig sind (z. B. ist das Ziel mitten im Meer). Für unsere Zwecke stellen wir nur sicher, dass es sich dabei um ein Tupel von Floats im Bereich von 0,0 bis 90,0 für den Breitengrad und -180,0 bis 180,0 für den Längengrad handelt.

Hier ist die Aktualisierung SelfDrivingCar Klasse. Ich habe einige der unimplementierten Methoden trivial implementiert, weil die Fahrt() Die Methode ruft einige dieser Methoden direkt oder indirekt auf.

Klasse SelfDrivingCar (object): def __init __ (self, object_detector): self.object_detector = object_detector self.speed = 0 self.destination = Keine def _accelerate (self): self.speed + = 1 def _decelerate (self): wenn self. speed> 0: self.speed - = 1 def _advance_to_destination (self): Abstand = self._calculate_distance_to_object_in_front () wenn Entfernung < 10: self.stop() elif distance < self.speed / 2: self._decelerate() elif self.speed < self._get_speed_limit(): self._accelerate() def _has_arrived(self): return True def _calculate_distance_to_object_in_front(self): return self.object_detector.calculate_distance_to_object_in_front() def _get_speed_limit(self): return 65 def stop(self): self.speed = 0 def drive(self, destination): self.destination = destination while not self._has_arrived(): self._advance_to_destination() self.stop()

Um die Fehlerbehandlung im Test zu testen, werde ich ungültige Argumente übergeben und überprüfen, ob sie ordnungsgemäß zurückgewiesen wurden. Sie können dies tun, indem Sie die self.assertRaises () Methode von unittest.TestCase. Diese Methode ist erfolgreich, wenn der getestete Code tatsächlich eine Ausnahme auslöst.

Lass es uns in Aktion sehen. Das Probefahrt() Methode übergibt Breiten- und Längengrade außerhalb des gültigen Bereichs und erwartet die Fahrt() Methode, um eine Ausnahme auszulösen.

von unittest importieren TestCase von self_driving_car importieren SelfDrivingCar-Klasse MockObjectDetector (Objekt): def berechnung_distanz_auf_objekt_in_front (self): Rückgabe von 20 Klassen SelfDrivingCarTest (TestCase): def setUp (self): self.car = SelfDrivingCar (MockObayr). self.car.speed = 5 self.car.stop () # Vergewissern Sie sich, dass die Geschwindigkeit 0 ist, nachdem Sie self.assertEqual (0, self.car.speed) gestoppt haben. # Vergewissern Sie sich, dass das Anhalten wieder in Ordnung ist, wenn das Fahrzeug bereits gestoppt ist. car.stop () self.assertEqual (0, self.car.speed) def test_drive (self): # Gültiges Ziel self.car.drive ((55.0, 66.0)) # Ungültiges Ziel falscher Bereich self.assertRaises (Ausnahme, self.) .Antrieb, (-55,0, 200,0))

Der Test schlägt fehl, weil die Fahrt() Die Methode überprüft ihre Argumente nicht auf Gültigkeit und löst keine Ausnahme aus. Sie erhalten einen schönen Bericht mit vollständigen Informationen darüber, was wo und warum fehlgeschlagen ist.

python -m unittest discover -v test_drive (untitled.test_self_driving_car.SelfDrivingCarTest)… FAIL test_stop (untitled.test_self_driving_car.SelfDrivingCarTest)… ok ========================= ================================================== FAIL: test_drive (untitled.test_self_driving_car.SelfDrivingCarTest) --------------------------------------- --------------------------- Traceback (letzter Aufruf zuletzt): Datei "/Users/gigi/PycharmProjects/untitled/test_self_driving_car.py" Zeile 29 in test_drive self.assertRaises (Ausnahme, self.car.drive, (-55.0, 200.0)) AssertionError: Ausnahme nicht ausgelöst -------------------- -------------------------------------------------- 2 Tests in 0,000s fehlgeschlagen (Fehler = 1)

Um dies zu beheben, aktualisieren wir das Fahrt() Methode, um den Bereich seiner Argumente tatsächlich zu überprüfen:

def drive (self, destination): lat, lon = destination falls nicht (0.0 <= lat <= 90.0): raise Exception('Latitude out of range') if not (-180.0 <= lon <= 180.0): raise Exception('Latitude out of range') self.destination = destination while not self._has_arrived(): self._advance_to_destination() self.stop()

Nun sind alle Tests bestanden.

python -m unittest discover -v test_drive (untitled.test_self_driving_car.SelfDrivingCarTest)… ok test_stop (untitled.test_self_driving_car.SelfDrivingCarTest)… ok ----------------------- ----------------------------------------------- 2 Tests durchgeführt in 0,000s OK 

Private Methoden testen

Sollten Sie jede Funktion und Methode testen? Sollten Sie insbesondere private Methoden testen, die nur von Ihrem Code aufgerufen werden? Die normalerweise unbefriedigende Antwort lautet: "Es kommt drauf an". 

Ich werde versuchen, hier nützlich zu sein und Ihnen sagen, worauf es ankommt. Sie wissen genau, wer Ihre private Methode aufruft - es ist Ihr eigener Code. Wenn Ihre Tests für die öffentlichen Methoden, die Ihre private Methode aufrufen, umfangreich sind, testen Sie Ihre privaten Methoden bereits ausführlich. Wenn eine private Methode jedoch sehr kompliziert ist, möchten Sie sie möglicherweise unabhängig testen. Benutze dein Urteilsvermögen.

So organisieren Sie Ihre Unit-Tests

In einem großen System ist nicht immer klar, wie Sie Ihre Tests organisieren. Sollten Sie eine große Datei mit allen Tests für ein Paket oder eine Testdatei für jede Klasse haben? Sollten sich die Tests in derselben Datei befinden wie der zu testende Code oder im selben Verzeichnis?

Hier ist das System, das ich benutze. Tests sollten vollständig vom getesteten Code getrennt sein (daher verwende ich kein doctest). Idealerweise sollte Ihr Code in einem Paket enthalten sein. Die Tests für jedes Paket sollten sich in einem gleichnamigen Verzeichnis Ihres Pakets befinden. Im Testverzeichnis sollte sich für jedes Modul Ihres Pakets eine Datei befinden Prüfung_

Wenn Sie zum Beispiel drei Module in Ihrem Paket haben: module_1.py, module_2.py und module_3.py, Sie sollten drei Testdateien haben: test_module_1.py, test_module_2.py und test_module_3.py unter dem Testverzeichnis. 

Diese Konvention hat mehrere Vorteile. Es macht nur durch das Durchsuchen von Verzeichnissen klar, dass Sie nicht vergessen haben, ein Modul vollständig zu testen. Es hilft auch, die Tests in Brocken mit angemessener Größe zu organisieren. Angenommen, Ihre Module haben eine angemessene Größe, dann befindet sich der Testcode für jedes Modul in einer eigenen Datei, die etwas größer sein kann als das zu testende Modul, aber dennoch etwas, das bequem in eine Datei passt. 

Fazit

Unit-Tests sind die Grundlage von Solid Code. In diesem Lernprogramm habe ich einige Grundsätze und Richtlinien für das Testen von Einheiten untersucht und die Gründe für einige bewährte Vorgehensweisen erläutert. Je größer das System, das Sie bauen, desto wichtiger werden die Komponententests. Aber Unit-Tests reichen nicht aus. Für große Systeme sind auch andere Arten von Tests erforderlich: Integrationstests, Leistungstests, Belastungstests, Durchdringungstests, Abnahmeprüfungen usw.