Übung
In dieser Übung verwenden Sie Pytest, um eine Funktion zu testen. Anschließend finden und beheben Sie einige potenzielle Probleme mit der Funktion, die zu fehlerhaften Tests führen. Beim Betrachten von Fehlern und dem Verwenden der umfassenden Fehlerberichterstattung von Pytest ist es wichtig, problematische Tests oder Fehler im Produktionscode zu identifizieren und zu beheben.
Für diese Übung verwenden wir eine Funktion namens admin_command()
, die einen Systembefehl als Eingabe entgegennimmt und optional mithilfe des sudo
-Tools mit einem Präfix versieht. Die Funktion weist einen Fehler auf, den Sie durch Schreiben von Tests ermitteln.
Schritt 1: Hinzufügen einer Datei mit Tests für diese Übung
Erstellen Sie eine neue Testdatei, indem Sie die Python-Konventionen für Dateinamen für Testdateien befolgen. Nennen Sie die Testdatei test_exercise.py, und fügen Sie den folgenden Code hinzu:
def admin_command(command, sudo=True): """ Prefix a command with `sudo` unless it is explicitly not needed. Expects `command` to be a list. """ if sudo: ["sudo"] + command return command
Die Funktion
admin_command()
verwendet eine Liste als Eingabe mithilfe descommand
-Arguments und kann optional die Liste mit dem Präfixsudo
versehen. Wenn dassudo
-Schlüsselwortargument aufFalse
festgelegt ist, gibt es denselben Befehl zurück, der als Eingabe angegeben wird.Fügen Sie in derselben Datei die Tests für die Funktion
admin_command()
an. Die Tests verwenden eine Hilfsmethode, die einen Beispielbefehl zurückgibt:class TestAdminCommand: def command(self): return ["ps", "aux"] def test_no_sudo(self): result = admin_command(self.command(), sudo=False) assert result == self.command() def test_sudo(self): result = admin_command(self.command(), sudo=True) expected = ["sudo"] + self.command() assert result == expected
Hinweis
Es ist nicht üblich, Tests innerhalb derselben Datei wie tatsächlichen Code aufzubewahren. Die Beispiele in dieser Übung verfügen der Einfachheit halber über tatsächlichen Code in derselben Datei. In realen Python-Projekten werden Tests in der Regel durch Dateien und Verzeichnisse vom Code getrennt, den sie testen.
Schritt 2: Ausführen der Tests und Ermitteln des Fehlers
Da die Testdatei nun eine Funktion zum Testen und eine Reihe von Tests zur Überprüfung ihres Verhaltens enthält, können Sie die Tests nun ausführen und mit Fehlern arbeiten.
Führen Sie die Datei mit Python aus:
$ pytest test_exercise.py
Der Lauf sollte mit einem bestandenen und einem fehlgeschlagenen Test abgeschlossen werden, und die Fehlerausgabe sollte ungefähr so aussehen wie die folgende Ausgabe:
=================================== FAILURES =================================== __________________________ TestAdminCommand.test_sudo __________________________ self = <test_exercise.TestAdminCommand object at 0x10634c2e0> def test_sudo(self): result = admin_command(self.command(), sudo=True) expected = ["sudo"] + self.command() > assert result == expected E AssertionError: assert ['ps', 'aux'] == ['sudo', 'ps', 'aux'] E At index 0 diff: 'ps' != 'sudo' E Right contains one more item: 'aux' E Use -v to get the full diff test_exercise.py:24: AssertionError =========================== short test summary info ============================ FAILED test_exercise.py::TestAdminCommand::test_sudo - AssertionError: assert... ========================= 1 failed, 1 passed in 0.04s ==========================
Fehler in der Ausgabe beim
test_sudo()
-Test. Pytest gibt Details zu den beiden verglichenen Listen an. In diesem Fall enthält dieresult
-Variable keinensudo
-Befehl. Das ist das erwartete Ergebnis des Tests.
Schritt 3: Beheben des Fehlers und erfolgreiche Durchführung der Tests
Bevor Sie Änderungen vornehmen, müssen Sie verstehen, warum ein Fehler auftritt. Auch wenn Sie sehen können, dass die Erwartung nicht erfüllt wird (sudo
ist nicht im Ergebnis enthalten), müssen Sie die Ursache herausfinden.
Sehen Sie sich die folgenden Codezeilen aus der admin_command()
-Funktion an, wenn die sudo=True
-Bedingung erfüllt ist:
if sudo:
["sudo"] + command
Der Vorgang der Listen wird nicht verwendet, um den Wert zurückzugeben. Da er nicht zurückgegeben wird, gibt die Funktion den Befehl letztlich stets ohne sudo
zurück.
Aktualisieren Sie die
admin_command()
-Funktion, damit der Listenvorgang zurückgegeben wird, sodass das geänderte Ergebnis beim Anfordern einessudo
-Befehls verwendet wird. Die aktualisierte Funktion sollte wie folgt aussehen:def admin_command(command, sudo=True): """ Prefix a command with `sudo` unless it is explicitly not needed. Expects `command` to be a list. """ if sudo: return ["sudo"] + command return command
Führen Sie den Test mit Pytest erneut aus. Versuchen Sie, die Ausführlichkeit der Ausgabe zu verbessern, indem Sie das
-v
-Flag mit Pytest verwenden:$ pytest -v test_exercise.py
Überprüfen Sie nun die Ausgabe. Nun sollten zwei bestandene Tests gezeigt werden:
============================= test session starts ============================== Python 3.9.6, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 cachedir: .pytest_cache rootdir: /private collected 2 items test_exercise.py::TestAdminCommand::test_no_sudo PASSED [ 50%] test_exercise.py::TestAdminCommand::test_sudo PASSED [100%] ============================== 2 passed in 0.00s ===============================
Hinweis
Da die Funktion mit mehreren Werten verschiedener Schreibung arbeiten kann, sollten weitere Tests hinzugefügt werden, um diese Variationen abzudecken. Dadurch kann in Zukunft verhindert werden, dass Änderungen an der Funktion ein anderes (unerwartetes) Verhalten hervorrufen.
Schritt 4: Hinzufügen von neuem Code mit Tests
Nach dem Hinzufügen von Tests in den vorherigen Schritten sollten Sie sich wohl fühlen, weitere Änderungen an der Funktion vorzunehmen und sie mit Tests zu überprüfen. Selbst wenn die Änderungen nicht durch vorhandene Tests abgedeckt sind, können Sie sicher sein, dass Sie gegen keine vorherigen Annahmen verstoßen.
In diesem Fall vertraut die admin_command()
-Funktion blind darauf, dass das command
-Argument stets eine Liste ist. Wir können dies verbessern, indem wir sicherstellen, dass eine Ausnahme mit einer hilfreichen Fehlermeldung ausgelöst wird.
Erstellen Sie zunächst einen Test, der das Verhalten erfasst. Obwohl die Funktion noch nicht aktualisiert wurde, versuchen Sie einen Test-First-Ansatz (auch als testgesteuerte Entwicklung oder TDD bezeichnet).
- Aktualisieren Sie die Datei test_exercise.py, damit sie
pytest
oben importiert. Dieser Test verwendet ein internes Hilfsprogramm aus dempytest
-Framework:
import pytest
- Fügen Sie nun einen neuen Test an die Klasse an, um die Ausnahme zu überprüfen. Dieser Test sollte eine
TypeError
von der Funktion erwarten, wenn der an sie übergebene Wert keine Liste ist:
def test_non_list_commands(self): with pytest.raises(TypeError): admin_command("some command", sudo=True)
- Aktualisieren Sie die Datei test_exercise.py, damit sie
Führen Sie die Tests erneut mit Pytest aus. Sie sollten sie alle bestehen:
============================= test session starts ============================== Python 3.9.6, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 rootdir: /private/ collected 3 items test_exercise.py ... [100%] ============================== 3 passed in 0.00s ===============================
Der Test ist gut genug für eine Überprüfung von
TypeError
. Es wäre allerdings gut, den Code mit einer hilfreichen Fehlermeldung hinzuzufügen.Aktualisieren Sie die Funktion, um
TypeError
explizit mit einer hilfreichen Fehlermeldung auszulösen:def admin_command(command, sudo=True): """ Prefix a command with `sudo` unless it is explicitly not needed. Expects `command` to be a list. """ if not isinstance(command, list): raise TypeError(f"was expecting command to be a list, but got a {type(command)}") if sudo: return ["sudo"] + command return command
Aktualisieren Sie schließlich die
test_non_list_commands()
-Methode, um nach der Fehlermeldung zu suchen:def test_non_list_commands(self): with pytest.raises(TypeError) as error: admin_command("some command", sudo=True) assert error.value.args[0] == "was expecting command to be a list, but got a <class 'str'>"
Der aktualisierte Test verwendet
error
als Variable, die alle Ausnahmeinformationen enthält. Mithilfe vonerror.value.args
können Sie die Argumente der Ausnahme untersuchen. In diesem Fall weist das erste Argument die Fehlerzeichenfolge auf, die der Test überprüfen kann.
Arbeit überprüfen
Jetzt sollten Sie eine Python-Testdatei namens test_exercise.py haben, die Folgendes enthält:
- Eine
admin_command()
-Funktion, die ein Argument akzeptiert, und ein Schlüsselwortargument - Eine
TypeError
-Ausnahme mit einer hilfreichen Fehlermeldung in deradmin_command()
-Funktion - Eine
TestAdminCommand()
-Testklasse mit einercommand()
-Hilfsmethode und drei Testmethoden, die dieadmin_command()
-Funktion überprüfen
Alle Tests sollten ohne Fehler übergeben werden, wenn Sie sie im Terminal ausführen.