Hallo meine lieben Freunde! Heute werde ich euch den faszinierenden Test-Philosophen vorstellen. Also macht euch bereit, denn wir tauchen in die Welt des automatischen Testens ein!
Die Metaflow Test Suite
Die Metaflow Test Suite ist ein umfangreiches Testsystem für die Kernkomponenten von Metaflow. Mit Hilfe von synthetischen Metaflow-Flows werden alle Aspekte von Metaflow getestet. Du kannst die Tests entweder manuell mit pytest oder run_tests.py ausführen.
Aber was passiert eigentlich, wenn du den Befehl python helloworld.py run
ausführst? Hierbei werden mehrere Schichten des Metaflow-Stacks durchlaufen. Diese Schichten reichen von der grundlegenden Python-Interpreter-Ebene bis hin zur Benutzeroberfläche.
Es wäre möglich, Unit-Tests für Funktionen in den Schichten 2, 3 und 6 zu schreiben, um einige Fehler zu erfassen. Allerdings treten die meisten Fehler durch unbeabsichtigte Interaktionen zwischen den Schichten auf. Zum Beispiel werden Ausnahmen, die durch das @catch
-Tag (Schicht 3) in einem tief verschachtelten Graphen (Schicht 4) abgefangen werden, möglicherweise nicht korrekt in der Client-API (Schicht 6) zurückgegeben, wenn Python 3 (Schicht 1) verwendet wird.
Die in das Core-Verzeichnis integrierte Testumgebung versucht, solche Fehler aufzudecken, indem automatisch Testfälle generiert werden, die auf den vom Entwickler bereitgestellten Spezifikationen basieren.
Spezifikationen
Die Testumgebung ermöglicht es dir, das Verhalten in vier Schichten anzupassen, die den oben genannten Schichten entsprechen:
- Du kannst die Ausführungsumgebung definieren, einschließlich Umgebungsvariablen, der Python Interpreter-Version und dem Typ des als Kontext verwendeten Datastore.
- Du definierst die Schritt-Funktionen, die Dekoratoren und die erwarteten Ergebnisse als MetaflowTest-Templates.
- Du definierst verschiedene Graphen, die den Schritt-Funktionen entsprechen und als einfache JSON-Beschreibungen der Graphenstruktur gespeichert sind.
- Du definierst verschiedene Möglichkeiten zur Überprüfung der Ergebnisse, die den verschiedenen Benutzeroberflächen von Metaflow als MetaflowCheck-Klassen entsprechen.
Die Testumgebung nimmt alle Kontexte, Graphen, Tests und Checkers entgegen und generiert für jede Kombination davon einen Test-Flow, sofern du nicht explizite Einschränkungen festgelegt hast. Die Test-Flows werden dann ausgeführt und die Ergebnisse werden gesammelt und zusammengefasst.
Kontexte
Die Kontexte werden in der Datei contexts.json
definiert. Die Datei ist selbsterklärend und muss in den meisten Fällen nicht bearbeitet werden, es sei denn, du fügst Tests für ein neues Kommandozeilenargument hinzu.
Beachte, dass einige Kontexte disabled: true
haben. Diese Kontexte werden standardmäßig nicht ausgeführt, wenn die Tests von einem CI-System ausgeführt werden. Du kannst sie jedoch auf der Befehlszeile für lokale Tests aktivieren.
Tests
Schau dir die Datei tests/basic_artifact.py
an. Dieser Test überprüft, ob die in einem Schritt definierten Artefakte in allen nachfolgenden Schritten verfügbar sind. Du kannst diesen einfachen Test als Vorlage für neue Tests verwenden.
Deine Testklasse sollte von MetaflowTest
abgeleitet sein. Die Klassenvariable PRIORITY
gibt an, wie grundlegend die getestete Funktionalität für Metaflow ist. Die Tests werden in aufsteigender Reihenfolge der Priorität ausgeführt, um sicherzustellen, dass die Grundlagen solide sind, bevor komplexere Fälle behandelt werden.
Die Schritt-Funktionen sind mit dem @steps
-Dekorator versehen. Beachte, dass im Gegensatz zu normalen Metaflow-Flows diese Funktionen auf mehrere Schritte in einem Graphen angewendet werden können. Eine Kernidee hinter dieser Testumgebung ist es, Graphen von Schritt-Funktionen zu entkoppeln, so dass verschiedene Kombinationen automatisch getestet werden können. Du musst daher Schritt-Funktionen bereitstellen, die auf verschiedene Schritttypen angewendet werden können.
Der @steps
-Dekorator hat zwei Argumente. Das erste Argument ist eine Ganzzahl, die die Rangfolge zwischen mehreren Schritt-Funktionen definiert, falls mehrere Schritt-Funktionen-Templates passen. Ein typisches Muster besteht darin, eine spezifische Funktion für einen spezifischen Schritttyp anzugeben, z. B. Joins, und ihr eine Priorität von 0 zu geben. Dann kann eine allgemeine Funktion mit @steps(2, ['all'])
definiert werden. Das Ergebnis ist, dass die spezielle Funktion auf Joins angewendet wird und die allgemeine Funktion für alle anderen Schritte.
Das zweite Argument ist eine Liste von Qualifikatoren, die angibt, auf welche Arten von Schritten diese Funktion angewendet werden kann. Es gibt eine Reihe von integrierten Qualifikatoren wie all
, start
, end
, join
, linear
, die den entsprechenden Schritttypen entsprechen. Neben diesen Qualifikatoren können Graphen beliebige benutzerdefinierte Qualifikatoren angeben.
Durch Angabe von required=True
als Schlüsselwortargument zu @steps
kannst du verlangen, dass eine bestimmte Schritt-Funktion in Kombination mit einem Graphen verwendet werden muss, um einen gültigen Testfall zu erzeugen. Durch Erstellen eines benutzerdefinierten Qualifikators und Festlegen von required=True
kannst du steuern, wie Tests mit Graphen abgeglichen werden.
Im Allgemeinen ist es vorteilhaft, Testfälle zu schreiben, die nicht zu restriktive Qualifikatoren und required=True
spezifizieren. Auf diese Weise wirfst du ein weites Netz, um Fehler mit vielen generierten Testfällen zu erkennen. Wenn der Test jedoch langsam ausgeführt wird und/oder nicht von einer großen Anzahl passender Graphen profitiert, ist es ratsam, ihn spezifischer zu machen.
Aussagen
Ein Testfall ist nicht sehr nützlich, wenn er seine Ergebnisse nicht überprüft. Es gibt zwei Möglichkeiten, um zu bestätigen, dass der Test wie erwartet funktioniert.
Du kannst die Funktion assert_equals(expected, got)
innerhalb von Schritt-Funktionen verwenden, um zu bestätigen, dass die Daten innerhalb der Schritt-Funktionen gültig sind. Zweitens kannst du eine Methode check_results(self, flow, checker)
in deiner Testklasse definieren, die die gespeicherten Ergebnisse nach erfolgreichem Durchlauf des Flows überprüft.
Verwende <CustomChecker>.assert_equals(expected, got)
, um zu überprüfen, ob die Schritte die erwarteten Datenartefakte enthalten.
Schau dir die vorhandenen Testfälle im tests
-Verzeichnis an, um eine Vorstellung davon zu bekommen, wie dies in der Praxis funktioniert.
Graphen
Graphen sind einfache JSON-Repräsentationen von gerichteten Graphen. Sie listen jeden Schritt in einem Graphen und die Übergänge zwischen ihnen auf. Jeder Schritt kann eine optionale Liste von benutzerdefinierten Qualifikatoren haben, wie oben beschrieben.
Sieh dir die vorhandenen Graphen im graphs
-Verzeichnis an, um eine Vorstellung von der Syntax zu bekommen.
Checkers
Der Test Harness führt derzeit zwei Arten von Benutzeroberflächen aus: Die Kommandozeilen-Schnittstelle und die Python-API. Wenn du Tests für neue Arten von Funktionen in der CLI und/oder der Python-API hinzufügen möchtest, solltest du eine neue Methode in der Basis-Klasse MetaflowCheck
und entsprechende Implementierungen in mli_check.py
und cli_check.py
hinzufügen. Wenn bestimmte Funktionen nur in einer der Schnittstellen verfügbar sind, kannst du eine Stummelimplementierung bereitstellen, die in der anderen Checker-Klasse True
zurückgibt.
Verwendung
Der Test Harness wird durch Ausführen von run_tests.py
gestartet. Standardmäßig werden alle gültigen Kombinationen von Kontexten, Tests, Graphen und Checkern ausgeführt. Dieser Modus eignet sich für automatisierte Tests, die von einem CI-System ausgeführt werden.
Bei lokalen Tests empfiehlt es sich, die Test-Suite wie folgt auszuführen:
python run_tests.py --contexts=dev_local --debug
Dies verwendet nur den dev_local
-Kontext, der keine Kommunikation über Netzwerke wie -metadata=service
oder -datastore=s3
erfordert. Der --debug
-Flag sorgt dafür, dass das Testsystem bei einem Fehler im ersten Testfall schnell abbricht. Der Standardmodus besteht darin, alle Testfälle auszuführen und alle Fehler am Ende zusammenzufassen.
Du kannst auch einen einzelnen Testfall wie folgt ausführen:
python run_tests.py --contexts=dev_local --graphs=my_graph --tests=my_test
Damit wählst du einen einzelnen Kontext, einen einzelnen Graphen und einen einzelnen Testfall aus. Dies ist der schnellste Weg, um den Testfall zu testen, wenn du einen neuen Test entwickelst.
Abdeckungsbericht
Der Test Harness verwendet das coverage
-Paket in Python, um einen Testabdeckungsbericht zu erstellen. Standardmäßig findest du nach Abschluss des Test Harness einen umfassenden Testabdeckungsbericht im Verzeichnis coverage
.
Nachdem du ein neues Feature in Metaflow entwickelt hast, verwende den line-by-line Testabdeckungsbericht, um sicherzustellen, dass alle Zeilen, die mit dem neuen Feature zusammenhängen, von den Tests abgedeckt werden.