Guten Tag! Ich möchte ein neues Tool für End-to-End-Tests von Microservices vorstellen - Catcher

Warum testen?
Warum brauche ich e2e-Tests? Martin Fowler empfiehlt , dies zugunsten einfacherer Tests zu vermeiden.
Je höher die Tests sind, desto weniger wird neu geschrieben. Unit-Tests werden fast vollständig neu geschrieben. Funktionstests müssen auch Ihre Zeit im Falle eines ernsthaften Refactorings verbringen. End-to-End-Tests sollten die Geschäftslogik testen und sie ändert sich seltener.
Darüber hinaus garantiert selbst eine vollständige Testabdeckung aller Mikrodienste nicht deren korrekte Interaktion. Entwickler implementieren das Protokoll möglicherweise falsch (Fehler im Namen / Datentyp).
Oder implementieren Sie neue Funktionen, die auf dem Datenschema aus der Dokumentation basieren, und überraschen Sie die Produktumgebung in Form von Fehlern bei der Nichtübereinstimmung des Schemas: ein Durcheinander in den Daten oder jemand, der vergessen hat, das Datenschema zu aktualisieren.
Und die Tests aller beteiligten Dienste werden umweltfreundlich sein.
Warum automatische Tests?
Wirklich. An meinem früheren Arbeitsplatz wurde entschieden, dass die Bereitstellung automatisierter Tests zu lang, schwierig und teuer ist. Das System ist nicht groß (10-15 Microservices mit einem gemeinsamen Kafka). CTO entschied, dass "die Tests nicht wichtig sind, die Hauptsache ist, dass das System funktioniert." Manuell in mehreren Umgebungen getestet.
Wie es aussah (allgemeiner Prozess):
- Stimmen Sie mit anderen Entwicklern überein (Einführung aller an der neuen Funktionalität beteiligten Microservices)
- Führen Sie alle Dienste aus
- Stellen Sie eine Verbindung zu Remote-Kafka her (double ssh in dmz)
- Stellen Sie eine Verbindung zu k8s-Protokollen her
- Manuell bilden und senden Sie Kafka eine Nachricht (Gott sei Dank Json)
- Sehen Sie sich die Protokolle an und versuchen Sie zu verstehen, ob es funktioniert hat oder nicht.
Und jetzt ist ein bisschen Teer in diesem Schokoladenfass: Die meisten Tests mussten von Benutzern erstellt werden, da die Wiederverwendung vorhandener Tests schwierig ist.
Erstens hatten mehrere Dienste aufgrund der Tatsache, dass das System verteilt ist, ihre eigenen Datenbanken, die Benutzerinformationen enthielten.
Zweitens wurde Kafka zur dauerhaften Datenspeicherung verwendet. Das heißt, Selbst wenn die Informationen in der Datenbank gelöscht / geändert werden, liest der Dienst sie beim Neustart immer noch zurück.
Wie sah die Registrierung eines neuen Testbenutzers (ungefähr) aus:
- Geben Sie alle Daten ein (Name, E-Mail usw.)
- Eingabe persönlicher Daten (Adresse, Telefon, Steuerinformationen)
- Eingabe von Bankdaten (eigentlich Bankdaten)
- Beantworte 20-40 Fragen (fühlst du schon Schmerzen?)
- Gehen Sie die IDNow- Identifikation durch (danke, sie haben sie in der Entwicklungsumgebung ausgeschaltet, danke, auf der Bühne sind es ungefähr 5 Minuten oder mehr, weil ihre Sandbox manchmal überlastet ist).
- In diesem Schritt ist die Eröffnung eines Kontos in einem Drittanbietersystem erforderlich, und über das Front-End kann nichts unternommen werden. Sie müssen per SSH zu Kafka gehen und als Mock-Server arbeiten (senden Sie eine Nachricht, dass das Konto geöffnet ist).
- Als nächstes müssen Sie zu einem anderen Front-End im persönlichen Konto des Moderators gehen und den Benutzer bestätigen.
Super, Benutzer ist registriert! Jetzt ein wenig in der Salbe fliegen: Einige Tests erfordern mehr als 1 Testbenutzer. Und manchmal schlagen die Tests beim ersten Mal fehl.
Und wie erfolgt die Überprüfung neuer Funktionen und die Bestätigung durch das Geschäftsteam?
Trotzdem muss in der folgenden Umgebung wiederholt werden.
Unnötig zu erwähnen, dass Sie sich nach einer Weile wie ein Affe fühlen, der nur das tut, was er drückt, und Benutzer registriert.
Einige andere Entwickler (normalerweise das Frontend) hatten Probleme, eine Verbindung zu kafka herzustellen. Und mit einem Fehler im Terminal mit einer Zeichenfolge von mehr als 80 Zeichen (nicht jeder wusste über tmux Bescheid).
Vorteile :
- Sie müssen nichts konfigurieren / schreiben. Testen Sie direkt in einer laufenden Umgebung.
- erfordert keine hohen Qualifikationen (billigere Spezialisten können das)
Nachteile :
- braucht viel Zeit (je weiter, desto mehr)
- In der Regel werden nur neue Funktionen getestet (es ist nicht klar, ob die vorhandene defekt ist).
- Qualifizierte Entwickler beschäftigen sich häufig mit manuellen Tests (teure Spezialisten erledigen billige Arbeit).
Wie automatisiere ich?
Wenn Sie dies lesen, indem Sie mit dem Kopf nicken und sagen: "Ja, es ist ein großartiger Prozess, Jungs wissen, was sie tun", dann werden Sie nicht interessiert sein.
Es gibt zwei Arten von hausgemachten e2e-Tests, die davon abhängen, welcher der Programmierer freier war:
- das Backend, das in Ihrer Testumgebung lebt. Es schützt die Testlogik, die durch Endpunkte zuckt. Aufgrund der Interaktion mit CI kann es sogar teilweise automatisiert werden.
- Ein Skript mit derselben verkabelten Logik. Der einzige Unterschied ist, dass Sie irgendwohin gehen und es von dort aus ausführen müssen. Wenn Sie Ihrem CI vertrauen, können Sie es sogar automatisch ausführen.
Das hört sich gut an. Probleme?
Ja, solche Tests werden auf das geschrieben, was die Person, die sie schreibt, weiß. In der Regel handelt es sich dabei um Skriptsprachen wie Rub oder Python, mit denen Sie solche Dinge schnell und einfach schreiben können. Manchmal kann man jedoch auf eine Reihe von Bash-Skripten stoßen, C oder etwas Exotischeres (ich habe eine Woche damit verbracht, mein Fahrrad mit Bash-Skripten nach Python zu kopieren, weil die Skripte nicht mehr erweiterbar waren und niemand wirklich wusste, wie sie funktionierten und was sie testeten). .
Projektbeispiel hier
Vorteile :
Nachteile :
- Zusätzliche Anforderungen an die Qualifikation von Entwicklern sind möglich (wenn sie in Java entwickeln und die Tests in Python geschrieben wurden).
- Schreiben von Code zum Testen von geschriebenem Code (wer testet die Tests?)
Ist etwas fertig?
Schauen Sie einfach in Richtung BDD . Es gibt eine Gurke , es gibt eine Lehre .
Kurz gesagt, der Entwickler beschreibt das Geschäftsszenario in einer speziellen Sprache und implementiert dann die Schritte des Skripts im Code. Die Sprache ist normalerweise für Menschen lesbar und es wird davon ausgegangen, dass sie nicht nur von Entwicklern, sondern auch von Projektmanagern gelesen / geschrieben wird.
Die Skripte befinden sich zusammen mit der Implementierung der Schritte in einem separaten Projekt und werden von Produkten von Drittanbietern (Cucumber / Gauge / ...) ausgeführt.
Das Skript sieht folgendermaßen aus:
Customer sign-up ================ * Go to sign up page Customer sign-up ---------------- tags: sign-up, customer * Sign up a new customer with name "John" email "jdoe@test.de" and "password" * Check if the sign up was successful
Und Umsetzung:
@Step("Sign up as <customer> with email <test@example.com> and <password>") public void signUp(String customer, String email, String password) { WebDriver webDriver = Driver.webDriver; WebElement form = webDriver.findElement(By.id("new_user")); form.findElement(By.name("user[username]")).sendKeys(customer); form.findElement(By.name("user[email]")).sendKeys(email); form.findElement(By.name("user[password]")).sendKeys(password); form.findElement(By.name("user[password_confirmation]")).sendKeys(password); form.findElement(By.name("commit")).click(); } @Step("Check if the sign up was successful") public void checkSignUpSuccessful() { WebDriver webDriver = Driver.webDriver; WebElement message = webDriver.findElements(By.className("message")); assertThat(message.getText(), is("You have been signed up successfully!")); }
Vollständiges Projekt hier
Vorteile :
- Geschäftslogik wird in einer für Menschen lesbaren Sprache beschrieben und an einem Ort gespeichert (kann als Dokumentation verwendet werden)
- Es werden vorgefertigte Lösungen verwendet, Entwickler müssen nur wissen, wie sie verwendet werden
Nachteile :
- Manager lesen und schreiben keine Skripte
- Sie müssen sowohl die Spezifikationen als auch deren Implementierung befolgen (und dies ist das Schreiben von Code und das Bearbeiten von Spezifikationen).
Nun, warum dann Catcher?
Natürlich, um den Prozess zu vereinfachen.
Der Entwickler schreibt nur Skripte in json / yaml und Catcher führt sie aus. Das Skript besteht aus nacheinander ausgeführten Schritten, zum Beispiel:
steps: - http: post: url: '127.0.0.1/save_data' body: {key: '1', data: 'foo'} - postgres: request: conf: 'dbname=test user=test host=localhost password=test' query: 'select * from test where id=1'
Catcher unterstützt jinja2-Vorlagen, sodass Sie im obigen Beispiel Variablen anstelle von verdrahteten Werten verwenden können. Globale Variablen können in Inventardateien (wie in einem Ensemble) gespeichert, aus der Umgebung abgerufen und neue registriert werden:
variables: bonus: 5000 initial_value: 1000 steps: - http: post: url: '{{ user_service }}/sign_up' body: {username: 'test_user_{{ RANDOM_INT }}', data: 'stub'} register: {user_id: '{{ OUTPUT.uuid }}' - kafka: consume: server: '{{ kafka }}' topic: '{{ new_users_topic }}' where: equals: {the: '{{ MESSAGE.uuid }}', is: '{{ user_id }}'} register: {balance: '{{ OUTPUT.initial_balance }}'}
Darüber hinaus können Sie Testschritte ausführen:
- check: # check user's initial balance equals: {the: '{{ balance }}', is: '{{ initial_value + bonus }}'}
Sie können einige Skripte auch von anderen Skripten aus ausführen, was sich hervorragend auf die Sauberkeit und Wiederverwendung des Codes auswirkt (einschließlich des Startens nur eines Teils der Schritte durch das Tag-System, des verzögerten Starts usw. Brötchen).
include: file: register_user.yaml as: sign_up steps: # .... some steps - run: include: sign_up # .... some steps
Das Einfügen und Verwenden von Skripten kann das Problem des Wartens auf eine Ressource lösen (warten Sie auf einen Dienst, während dieser gestartet wird).
Zusätzlich zu vorgefertigten integrierten Schritten und einem zusätzlichen Repository können Sie Ihre Module in Python (nur von ExternalStep erben) oder in einer anderen Sprache schreiben:
und verwenden:
--- variables: one: 1 two: 2 steps: - math: add: {the: '{{ one }}', to: '{{ two }}'} register: {sum: '{{ OUTPUT }}'}
Skripte werden in die Docker-Datei eingefügt und über CI ausgeführt.
Auch dieses Bild kann in Marathon / K8s verwendet werden, um die vorhandene Umgebung zu testen. Im Moment arbeite ich an einem Backend (ähnlich wie AnsibleTower), um den Testprozess noch einfacher und bequemer zu gestalten.
Vorteile :
- Keine Notwendigkeit, Code zu schreiben (nur bei benutzerdefinierten Modulen)
- Wechseln der Umgebung durch Inventardateien (wie im Ensemble)
- Sie können Ihre eigenen Module verwenden (in jeder Sprache, auch sh)
Nachteile :
- Nicht vom Menschen lesbare Syntax (im Vergleich zu BDD-Tools)
Anstelle einer Schlussfolgerung
Als ich dieses Tool schrieb, wollte ich nur die Zeit reduzieren, die ich normalerweise für Tests verbringe. So kam es, dass jedes neue Unternehmen ein solches System schreiben (oder neu schreiben) musste.
Das Tool erwies sich jedoch als flexibler als erwartet. Wenn sich jemand für den Artikel (oder das Tool selbst) interessiert, kann ich Ihnen sagen, wie Sie mit Catcher zentralisierte Migrationen organisieren und das Microservice-System aktualisieren können.
Upd
Wie ich in den Kommentaren angegeben wurde, wird das Thema nicht bekannt gegeben.
Ich werde versuchen, hier die umstrittensten Thesen anzugeben.
- End-to-End-Tests sind keine Komponententests. Ich habe in diesem Artikel bereits auf M. Fowler Bezug genommen. Unit-Tests befinden sich im Test-Backend-Projekt (Standard-
tests
) und werden jedes Mal ausgeführt, wenn sich der Code in CI ändert. Und e2e-Tests sind ein separates Projekt. Sie dauern normalerweise länger, testen die Interaktion aller teilnehmenden Dienste und wissen nichts über den Code Ihres Projekts (Black Box). - Sie sollten Catcher nicht für Integrationstests (und darunter) verwenden. Ist es teuer. Es ist viel schneller, einen Test für das aktuelle Backend auf Ihrem YP zu schreiben. Sie benötigen End-to-End-Tests nur, wenn Ihre Geschäftslogik auf zwei oder mehr Services verteilt ist.
- Catcher ist auch ein BDD. Aus meiner Sicht besteht der Hauptvorteil gegenüber Gauge / Cucumber in den vorgefertigten Modulen und der einfachen Hinzufügung. Im Idealfall wird nur ein Test geschrieben. In der letzten Firma habe ich alle 4 Tests an Standardkomponenten geschrieben, ohne etwas zu programmieren. Dementsprechend werden die Qualifikationsanforderungen (und der Preis eines solchen Spezialisten) niedriger sein. Es sind nur Kenntnisse über json / yaml und die Fähigkeit zum Lesen von Spezifikationen erforderlich.
- Um Catcher-Tests zu schreiben, müssen Sie Catcher-DSL lernen. Leider ist es wahr. Zuerst wollte ich die Tests selbst schreiben lassen, direkt vom Mikrofon. Aber dann dachte ich, dass sie mich dann als unnötig entlassen würden;) Wie oben erwähnt - Catcher DSL ist die Standardspezifikation für json / yaml und step. Nichts grundlegend Neues.
- Sie können Standardtechnologien verwenden und etwas Eigenes schreiben. Wir sprechen jedoch über Microservices. Dies ist eine große Anzahl verschiedener Technologien und Atomwaffen und eine große Anzahl von Teams. Und wenn für den Java-Befehl junit + testcontainers die offensichtliche Wahl ist, wird das erlang-Team etwas anderes wählen. In einem großen Unternehmen mit mehr als 30 Teams an der Spitze entscheiden sie, dass alle Tests dem neuen Infrastruktur- / Qa-Team vorgelegt werden sollen. Können Sie sich vorstellen, wie glücklich sie in diesem Zoo sind?
- Wenn Sie 4-5 e2e-Tests haben, können Sie alles in einer beliebigen Skriptsprache schreiben und vergessen. Wenn sich die Logik jedoch im Laufe der Zeit ändert, müssen Sie nach 2 bis 4 Jahren eine Umgestaltung vornehmen, die Geschäftslogik der Tests direkt verteilt und Zugriffsmethoden auf die getesteten Komponenten implementiert. Am Ende schreibst du also deinen Catcher, nur nicht so flexibel. Ich habe 4 Implementierungen gebraucht, um das zu verstehen;)