EinfĂŒhrung in das Testen in Python. Teil 2

Hallo allerseits!

Wir setzen den Artikel ĂŒber das Kennenlernen von Python-Tests fort, den wir im Rahmen unseres Python-Entwicklerkurses fĂŒr Sie vorbereitet haben.

Testen auf Django und Flask Web Framework

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

Wie sie sich von anderen Anwendungen unterscheiden

Denken Sie an den Code, den Sie in einer Webanwendung testen möchten. Alle Routen, Ansichten und Modelle erfordern viel Import und Wissen ĂŒber das verwendete Framework.
Dies Ă€hnelt dem Testen eines Autos, das im ersten Teil des Tutorials beschrieben wurde: Bevor Sie einfache Tests durchfĂŒhren, z. B. das ÜberprĂŒfen von Scheinwerfern, mĂŒssen Sie den Computer im Auto einschalten.

Django und Flask vereinfachen diese Aufgabe und bieten ein unittest-basiertes Testframework. Sie können weiterhin Tests auf die ĂŒbliche Weise schreiben, diese jedoch etwas anders ausfĂŒhren.



Verwendung des Django Test Executor

Die Django-Startanwendungsvorlage erstellt die Datei tests.py in Ihrem Anwendungsverzeichnis. Wenn es noch nicht vorhanden ist, erstellen Sie es mit den folgenden Inhalten:

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

Der Hauptunterschied zu frĂŒheren Beispielen besteht darin, dass Sie von django.test.TestCase erben django.test.TestCase , nicht von django.test.TestCase . Die API dieser Klassen ist dieselbe, aber die Django TestCase-Klasse richtet alles zum Testen ein.

Verwenden manage.py zum AusfĂŒhren einer Testsuite manage.py test 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 __init__.py und erstellen Sie test_*.py Dateien. Django wird sie erkennen und ausfĂŒhren.

Weitere Informationen finden Sie auf der Django-Dokumentationsseite .

Verwendung von Unittest und Flask

Um mit Flask arbeiten zu können, muss eine Anwendung importiert und in den Testmodus versetzt werden. Sie können einen Testclient erstellen und damit Anforderungen an beliebige Routen in Ihrer Anwendung senden.

Der Testclient wird in der setUp-Methode Ihres Testfalls instanziiert. Im folgenden Beispiel ist my_app der Name der Anwendung. Machen Sie sich keine Sorgen, wenn Sie nicht wissen, was setUp tut. Wir werden uns den Abschnitt "Erweiterte Testskripte" genauer ansehen.
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 

Sie können dann TestfÀlle mit dem python -m unittest discover.

Weitere Informationen finden Sie auf der Flask-Dokumentationsseite.

Erweiterte Testskripte

Beachten Sie die drei Hauptschritte eines Tests, bevor Sie mit dem Erstellen von Tests fĂŒr Ihre Anwendung beginnen:

  1. Erstellung von Eingabeparametern;
  2. CodeausfĂŒhrung, Empfang von Ausgabedaten;
  3. Vergleich der Ausgabedaten mit dem erwarteten Ergebnis;

Dies kann komplizierter sein als das Erstellen eines statischen Werts fĂŒr die 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 als Fixture bezeichnet. Das Erstellen und Wiederverwenden von Fixtures ist eine gÀngige Praxis.

Das mehrfache AusfĂŒhren desselben Tests mit unterschiedlichen Werten in Erwartung des gleichen Ergebnisses wird als Parametrisierung bezeichnet.

Umgang mit erwarteten Fehlern

Als wir zuvor eine Liste von Skripten zum Testen von sum() , stellte sich die Frage: Was passiert, wenn wir einen schlechten Wert angeben, z. B. eine einzelne Ganzzahl oder eine Zeichenfolge?

In diesem Fall wird erwartet, dass sum() einen Fehler auslöst. Wenn ein Fehler auftritt, schlÀgt der Test fehl.

Es gibt eine spezielle Möglichkeit, mit erwarteten Fehlern umzugehen. Sie können .assertRaises() als Kontextmanager verwenden und dann Testschritte im with Block 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 bestanden, wenn sum(data) einen TypeError auslöst. Sie können TypeError durch einen anderen Ausnahmetyp ersetzen.

Isolation des Anwendungsverhaltens

Im letzten Teil des Tutorials haben wir ĂŒber Nebenwirkungen gesprochen. Sie erschweren das Testen von Einheiten, da jeder Testlauf zu einem anderen oder einem schlechteren Ergebnis fĂŒhren 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 gemĂ€ĂŸ dem Prinzip der Einzelverantwortung;
  • Verspotten aller Methoden und Funktionsaufrufe, um Nebenwirkungen zu beseitigen;
  • Verwenden von Integrationstests anstelle von Komponententests fĂŒr dieses Fragment der Anwendung.
  • Wenn Sie mit dem Rauchen nicht vertraut sind, sehen Sie sich einige großartige Beispiele fĂŒr Python-CLI-Tests an .

Integrationstests schreiben

Bisher haben wir Unit-Tests mehr Aufmerksamkeit geschenkt. Unit-Tests sind eine großartige Möglichkeit, vorhersehbaren und stabilen Code zu erstellen. Aber am Ende sollte Ihre Anwendung beim Start funktionieren!

Integrationstests sind erforderlich, um die Zusammenarbeit mehrerer Anwendungskomponenten zu ĂŒberprĂŒfen. FĂŒr solche Tests muss möglicherweise die Rolle des KĂ€ufers oder Benutzers ausgeĂŒbt werden:

  • Rufen Sie die HTTP-REST-API auf.
  • Python-API-Aufruf;
  • Web-Service-Aufruf;
  • FĂŒhren Sie die Befehlszeile aus.

Alle diese Arten von Integrationstests können auf die gleiche Weise wie Komponententests gemĂ€ĂŸ der Vorlage Eingabeparameter, AusfĂŒhrung, Genehmigung geschrieben werden. Der wichtigste Unterschied besteht darin, dass Integrationstests gleichzeitig mehr Komponenten testen, was bedeutet, dass sie zu mehr Nebenwirkungen fĂŒhren als Komponententests. DarĂŒber hinaus erfordern Integrationstests mehr GerĂ€te, z. B. eine Datenbank, einen Netzwerk-Socket oder eine Konfigurationsdatei.

Daher wird empfohlen, Unit-Tests und Integrationstests zu trennen. Das Erstellen von Fixtures fĂŒr Integrations-Fixtures, z. B. eine Testdatenbank oder TestfĂ€lle selbst, dauert viel lĂ€nger als das DurchfĂŒhren von Unit-Tests. Daher sollten Sie Integrationstests durchfĂŒhren, bevor Sie in die Produktion gehen, anstatt sie jedes Mal auszufĂŒhren, wenn Sie einen Commit ausfĂŒhren.

Die einfachste Möglichkeit, Unit- und Integrationstests zu trennen, besteht darin, sie in verschiedenen Ordnern abzulegen.

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


Sie können eine bestimmte Gruppe von Tests auf verschiedene Arten ausfĂŒhren. Ein Flag zur Angabe des Quellverzeichnisses -s kann hinzugefĂŒgt werden, um die Erkennung mit einem Pfad zu vereinen, der Tests enthĂ€lt:

 $ python -m unittest discover -s tests/integration 

unittest zeigt alle Ergebnisse im Test- / Integrationsverzeichnis an.

Testen datenorientierter Anwendungen

Viele Integrationstests erfordern Backend-Daten, z. B. eine Datenbank mit bestimmten Werten. Angenommen, Sie benötigen einen Test, um den korrekten Betrieb der Anwendung mit mehr als 100 Kunden in der Datenbank zu ĂŒberprĂŒfen oder um die Richtigkeit der Anzeige der Bestellseite zu ĂŒberprĂŒfen, selbst wenn alle Namen der Waren auf Japanisch sind.

Diese Arten von Integrationstests hÀngen von verschiedenen Testvorrichtungen ab, um ihre Wiederholbarkeit und Vorhersagbarkeit sicherzustellen.

Testdaten sollten im Ordner "Fixtures" im Verzeichnis "Integrationstests" gespeichert werden, um ihre "Testbarkeit" hervorzuheben. Dann können Sie in den Tests die Daten laden und den Test ausfĂŒhren.

Hier ist ein Beispiel fĂŒr eine Datenstruktur, die aus JSON-Dateien besteht:

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


Im Testfall können Sie die Methode .setUp () verwenden, um Testdaten auf bekannte 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 Python-Datei speichern können. Unittest findet sie und fĂŒhrt sie aus. Sie können einen Testfall fĂŒr jeden Satz von Testdaten haben:

 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 Remotestandort abhĂ€ngt, z. B. von einer Remote-API, stellen Sie sicher, dass die Tests wiederholbar sind. Die Entwicklung kann sich aufgrund von Tests verzögern, die beim Deaktivieren der API und bei Kommunikationsproblemen fehlgeschlagen sind. In solchen FĂ€llen ist es besser, Remote-GerĂ€te lokal zu speichern, um sie zurĂŒckzurufen und an die Anwendung zu senden.

Die requests verfĂŒgt ĂŒber ein kostenloses Antwortpaket, mit dem Sie Antwortvorrichtungen erstellen und in Testordnern speichern können. Weitere Informationen finden Sie auf der GitHub-Seite .

Im nÀchsten Teil geht es um das Testen in verschiedenen Umgebungen und das Testen der Automatisierung.

DAS ENDE

Kommentare / Fragen sind immer willkommen. Hier oder an einem Tag der offenen TĂŒr nach Stas .

Source: https://habr.com/ru/post/de433572/


All Articles