Einführung in das Testen in Python. Teil 2

Published on December 18, 2018

Einführung in das Testen in Python. Teil 2

Ursprünglicher Autor: Anthony Shaw
  • Übersetzung
Hallo an alle!

Wir setzen den Artikel über die Vertrautheit mit dem Testen in Python fort, den wir im Rahmen unseres Kurses „Python Developer“ für Sie vorbereitet haben .

Testen auf Django- und Flask-Webrahmen

Wenn Sie Tests für Webanwendungen mit einem der gängigen Frameworks erstellen, z. B. Django oder Flask, sollten Sie die wichtigen Unterschiede beim Schreiben und Ausführen solcher Tests berücksichtigen.

Wie unterscheiden sie sich von anderen Anwendungen?

Denken Sie über den Code nach, den Sie in einer Webanwendung testen müssen. Alle Routen, Ansichten und Modelle erfordern viele Importe und Kenntnisse über das verwendete Framework.
Dies ist vergleichbar mit dem Testen des Fahrzeugs, das im ersten Teil des Tutorials erläutert wurde: Bevor Sie einfache Tests durchführen, z. B. die Funktionsweise von Scheinwerfern, müssen Sie den Computer im Auto einschalten.

Django und Flask vereinfachen diese Aufgabe und bieten ein Test-Framework, das nur auf Tests basiert. Sie können Tests wie gewohnt fortsetzen, sie jedoch etwas anders ausführen.



So verwenden Sie den Django-Testläufer

Die Django-Startvorlage erstellt eine Datei tests.py in Ihrem Anwendungsverzeichnis. Falls noch nicht vorhanden, erstellen Sie es mit folgendem Inhalt:

from django.test import TestCase
class MyTestCase(TestCase):
    # Your test methods

Der Hauptunterschied zu den vorherigen Beispielen besteht darin, von zu erben django.test.TestCaseund nicht unittest.TestCase. Die API dieser Klassen ist gleich, aber die Django TestCase-Klasse konfiguriert alles zum Testen.

Um die Testsuite auszuführen, verwenden Sie manage.pytest anstelle von unittest in der Befehlszeile:

$ python manage.py test

Wenn Sie mehrere Testdateien benötigen, ersetzen Sie tests.py durch den Testordner, fügen Sie eine leere Datei mit dem Namen ein __init__.pyund erstellen Sie die Dateien test_*.py. Django wird sie erkennen und ausführen.

Weitere Informationen finden Sie auf der Django-Dokumentationsseite .

Verwenden von unittest und Flask

Um mit Flask arbeiten zu können, muss die Anwendung importiert und in den Testmodus übertragen werden. Sie können einen Test-Client erstellen und damit Anfragen an beliebige Routen in Ihrer Anwendung senden.

Die Instantiierung eines Testclients erfolgt in der setUp-Methode Ihres Testfalls. Im folgenden Beispiel ist my_app der Name der Anwendung. Machen Sie sich keine Sorgen, wenn Sie nicht wissen, was setUp macht. Wir werden dies im Abschnitt "Fortgeschrittene Testskripte" kennenlernen.
Der Code in der Testdatei sieht folgendermaßen aus:

import my_app
import unittest
class MyTestCase(unittest.TestCase):
    def setUp(self):
        my_app.app.testing = True
        self.app = my_app.app.test_client()
    def test_home(self):
        result = self.app.get('/')
        # Make your assertions

Dann können Testfälle mit dem Befehl python -m unittest discover.

Weitere Informationen ausgeführt werden , der auf der Flask-Dokumentationswebsite verfügbar ist.

Erweiterte Testszenarien

Bevor Sie mit der Erstellung von Tests für Ihre Anwendung beginnen, müssen Sie die drei grundlegenden Schritte eines Tests berücksichtigen:

  1. Erstellung von Eingabeparametern;
  2. Ausführung des Codes, Erhalt der Datenausgabe;
  3. Vergleichen von Ausgangsdaten mit einem erwarteten Ergebnis;

Dies kann komplizierter sein als das Erstellen eines statischen Werts für Quelldaten wie eine Zeichenfolge oder eine Zahl. Manchmal erfordert Ihre Anwendung eine Instanz einer Klasse oder eines Kontexts. Was ist in diesem Fall zu tun?

Die Daten, die Sie als Quelle erstellen, werden Fixture genannt. Das Erstellen und Wiederverwenden von Geräten ist üblich.

Den gleichen Test mehrmals mit unterschiedlichen Werten auszuführen, um das gleiche Ergebnis zu erwarten, wird als Parametrisierung bezeichnet.

Umgang mit erwarteten Abstürzen

Bei der Erstellung einer Liste von Szenarien zum Testen sum()stellte sich die Frage: Was passiert, wenn wir einen falschen Wert angeben, z. B. eine einzelne Ganzzahl oder eine Zeichenfolge?

In diesem Fall wird sum()ein Fehler erwartet . Wenn ein Fehler auftritt, schlägt der Test fehl.

Es gibt einen bestimmten Weg, um die erwarteten Fehler zu behandeln. Sie können es .assertRaises()als Kontextmanager verwenden und dann with Testschritte innerhalb des Blocks ausführen:

import unittest
from my_sum import sum
class TestSum(unittest.TestCase):
    def test_list_int(self):
        """
        Тестируем, что удастся суммировать список целых чисел
        """
        data = [1, 2, 3]
        result = sum(data)
        self.assertEqual(result, 6)
    def test_list_fraction(self):
        """
        Тестируем, что удастся суммировать список дробных чисел
        """
        data = [Fraction(1, 4), Fraction(1, 4), Fraction(2, 5)]
        result = sum(data)
        self.assertEqual(result, 1)
    def test_bad_type(self):
        data = "banana"
        with self.assertRaises(TypeError):
            result = sum(data)
if __name__ == '__main__':
    unittest.main()

Dieser Testfall wird nur übergeben, wenn sum(data) TypeError ausgelöst wird. Sie können TypeError durch einen anderen Ausnahmetyp ersetzen.

Verhaltensisolierung im Anhang

Im letzten Teil des Tutorials haben wir über Nebenwirkungen gesprochen. Sie erschweren den Gerätetest, da bei jedem Testlauf ein anderes Ergebnis erzielt werden kann oder ein schlechteres Ergebnis erzielt werden kann: Ein Test kann den Status der gesamten Anwendung beeinflussen und dazu führen, dass ein anderer Test fehlschlägt!

Es gibt einige einfache Techniken zum Testen von Teilen einer Anwendung mit vielen Nebenwirkungen:

  • Code-Refactoring nach dem Grundsatz der einheitlichen Verantwortung;
  • Verspottung aller Methoden und Funktionsaufrufe zur Beseitigung von Nebenwirkungen;
  • Verwendung von Integrationstests anstelle von modular für diesen Teil der Anwendung.
  • Wenn Sie sich mit Spott nicht auskennen, finden Sie großartige Beispiele für Python CLI-Tests .

Integrationstests schreiben

Bisher haben wir Unit-Tests mehr Aufmerksamkeit gewidmet. Unit-Tests sind eine hervorragende Möglichkeit, vorhersagbaren und stabilen Code zu erstellen. Aber am Ende sollte Ihre Anwendung beim Start funktionieren!

Integrationstests sind erforderlich, um die Zusammenarbeit mehrerer Anwendungskomponenten zu testen. Für solche Tests kann es erforderlich sein, als Käufer oder Benutzer zu agieren:

  • Rufen Sie die HTTP-REST-API auf.
  • Python-API-Aufruf;
  • Rufen Sie einen Webservice an.
  • Befehlszeile ausführen

Alle diese Arten von Integrationstests können wie modulare Tests nach dem Muster Einführung Parameter, Ausführung, Validierung geschrieben werden. Der wichtigste Unterschied besteht darin, dass Integrationstests gleichzeitig mehr Komponenten prüfen und daher zu mehr Nebenwirkungen als Einzeltests führen. Darüber hinaus benötigen Integrationstests mehr Geräte, beispielsweise eine Datenbank, einen Netzwerksockel oder eine Konfigurationsdatei.

Daher wird empfohlen, Unit-Tests und Integrationstests voneinander zu trennen. Das Erstellen von Fixtures für die Integration, z. B. eine Testdatenbank oder Testfälle selbst, dauert viel länger als das Durchführen von Komponententests. Daher lohnt es sich, Integrationstests durchzuführen, bevor die Produktion beginnt, anstatt sie bei jedem Commit zu starten.

Die einfachste Möglichkeit, modulare Tests und Integrationstests voneinander zu trennen, besteht darin, sie auf verschiedene Ordner zu verteilen. Sie können eine bestimmte Gruppe von Tests auf verschiedene Arten ausführen. Ein Flag zum Angeben des Quellverzeichnisses (-s) kann hinzugefügt werden, um die Erkennung anhand eines Pfads mit Tests zu überprüfen:

project/

├── my_app/
│ └── __init__.py

└── tests/
|
├── unit/
| ├── __init__.py
| └── test_sum.py
|
└── integration/
├── __init__.py
└── test_integration.py





$ python -m unittest discover -s tests/integration

unittest zeigt alle Ergebnisse im Verzeichnis tests / integration an.

Testen datenorientierter Anwendungen

Viele Integrationstests erfordern Back-End-Daten, beispielsweise eine Datenbank mit bestimmten Werten. Stellen Sie sich vor, Sie benötigen einen Test, um zu überprüfen, ob die Anwendung mit mehr als 100 Kunden in der Datenbank ordnungsgemäß funktioniert, oder um die Korrektheit der Anzeige der Bestellseite zu überprüfen, selbst wenn alle Namen der Waren auf Japanisch lauten.

Diese Art von Integrationstests hängt von verschiedenen Testvorrichtungen ab, um deren Wiederholbarkeit und Vorhersagbarkeit sicherzustellen.

Die Testdaten sollten im Fixture-Ordner im Integrationstestverzeichnis gespeichert werden, um deren „Testbarkeit“ zu betonen. In den Tests können Sie dann die Daten herunterladen und den Test ausführen.

Hier ein Beispiel für eine Datenstruktur, die aus JSON-Dateien besteht: Im Testfall können Sie die .setUp () -Methode verwenden, um Testdaten in bekannter Weise aus der Fixture-Datei zu laden und mehrere Tests mit diesen Daten auszuführen. Denken Sie daran, dass Sie mehrere Testfälle in einer einzigen Python-Datei speichern können. Diese werden von unittest gefunden und ausgeführt. Sie können einen Testfall für jeden Testdatensatz haben:

project/

├── my_app/
│ └── __init__.py

└── tests/
|
└── unit/
| ├── __init__.py
| └── test_sum.py
|
└── integration/
|
├── fixtures/
| ├── test_basic.json
| └── test_complex.json
|
├── __init__.py
└── test_integration.py




import unittest
class TestBasic(unittest.TestCase):
    def setUp(self):
        # Load test data
        self.app = App(database='fixtures/test_basic.json')
    def test_customer_count(self):
        self.assertEqual(len(self.app.customers), 100)
    def test_existence_of_customer(self):
        customer = self.app.get_customer(id=10)
        self.assertEqual(customer.name, "Org XYZ")
        self.assertEqual(customer.address, "10 Red Road, Reading")
class TestComplexData(unittest.TestCase):
    def setUp(self):
        # load test data
        self.app = App(database='fixtures/test_complex.json')
    def test_customer_count(self):
        self.assertEqual(len(self.app.customers), 10000)
    def test_existence_of_customer(self):
        customer = self.app.get_customer(id=9999)
        self.assertEqual(customer.name, u"バナナ")
        self.assertEqual(customer.address, "10 Red Road, Akihabara, Tokyo")
if __name__ == '__main__':
    unittest.main()

Wenn Ihre Anwendung von Daten von einem Remote-Standort (z. B. einer Remote-API) abhängig ist, müssen Sie sicherstellen, dass die Tests wiederholbar sind. Die Entwicklung kann aufgrund von Tests verzögert werden, die fehlschlagen, wenn die API deaktiviert ist und Kommunikationsprobleme auftreten. In solchen Fällen ist es besser, die entfernten Geräte lokal zu speichern, um sie erneut anzurufen und an die Anwendung zu senden.

Die Bibliothek requests verfügt über ein kostenloses Antwortpaket, mit dem Sie Antwort-Fixtures erstellen und in Testordnern speichern können. Erfahren Sie mehr auf ihrer GitHub-Seite .

Der nächste Teil befasst sich mit dem Testen in mehreren Umgebungen und der Testautomatisierung.

THE END

Kommentare / Fragen sind wie immer willkommen. Hier oder für einen Tag der offenen Tür nach Stas .