Gonkey testet unsere Microservices in Lamoda, und wir dachten, er könnte auch Ihre testen, also haben wir sie in Open Source veröffentlicht . Wenn die Funktionalität Ihrer Dienste hauptsächlich über die API implementiert wird und JSON für den Datenaustausch verwendet wird, ist Gonkey mit ziemlicher Sicherheit für Sie geeignet.

Im Folgenden werde ich ausführlicher darauf eingehen und anhand konkreter Beispiele zeigen, wie man es benutzt.
Wie Gonkey geboren wurde
Wir haben mehr als hundert Mikrodienste, von denen jeder eine bestimmte Aufgabe löst. Alle Dienste haben eine API. Natürlich sind einige von ihnen auch eine Benutzeroberfläche, aber ihre Hauptaufgabe besteht darin, eine Datenquelle für eine Site, mobile Anwendungen oder andere interne Dienste zu sein und daher eine Softwareschnittstelle bereitzustellen .
Als wir feststellten, dass es viele Services gab und es dann noch mehr davon geben würde, entwickelten wir ein internes Dokument, das den Standardansatz für das API-Design beschreibt, und nahmen Swagger als Beschreibungstool (und schrieben sogar Dienstprogramme zum Generieren von Code basierend auf der Swagger-Spezifikation). Wenn Sie mehr darüber erfahren möchten, lesen Sie Andrews Vortrag mit Highload ++.
Der Standardansatz für das API-Design führte natürlich zur Idee eines Standardansatzes für das Testen. Folgendes wollte ich erreichen:
- Testen Sie Dienste über die API , da fast alle Funktionen des Dienstes über diese API implementiert werden
- Die Möglichkeit, den Start von Tests zu automatisieren, um sie in unseren CI / CD-Prozess zu integrieren, wie sie sagen: "Run by Button"
- Das Schreiben von Tests sollte veräußerlich sein , das heißt, damit nicht nur ein Programmierer Tests schreiben kann, idealerweise eine Person, die mit dem Programmieren nicht vertraut ist.
Also wurde Gonkey geboren.
Was ist das?
Gonkey ist eine Bibliothek (für Projekte in Golang) und ein Konsolendienstprogramm (für Projekte in allen Sprachen und Technologien), mit dem Sie Funktions- und Regressionstests von Diensten durchführen können, indem Sie gemäß einem vordefinierten Skript auf deren API zugreifen. Testskripte werden in YAML-Dateien beschrieben.
Einfach ausgedrückt, Gonkey kann:
- Bombardieren Sie Ihren Dienst mit HTTP-Anfragen und stellen Sie sicher, dass die Antworten wie erwartet sind. Es wird davon ausgegangen, dass JSON in Anfragen und Antworten verwendet wird, aber höchstwahrscheinlich funktioniert es in einfachen Fällen mit Antworten in einem anderen Format.
- Bereiten Sie die Datenbank für den Test vor, indem Sie sie mit Daten von Fixtures füllen (auch in YAML-Dateien angegeben).
- Imitieren Sie die Antworten externer Dienste mithilfe von Mocks (diese Funktion ist nur verfügbar, wenn Sie Gonkey als Bibliothek verbinden).
- Geben Sie der Konsole Testergebnisse oder generieren Sie einen Allure-Bericht.
Projekt-Repository
Docker-Bild
Beispiel für einen Servicetest mit Gonkey
Um Sie nicht mit Text zu belasten, möchte ich hier von Wörtern zu Taten wechseln und einige APIs testen und dabei erzählen und zeigen, wie Testskripte geschrieben werden.
Lassen Sie uns einen kleinen Service auf Go skizzieren, der die Arbeit einer Ampel simuliert. Es speichert die Farbe des aktuellen Signals: rot, gelb oder grün. Sie können die aktuelle Signalfarbe abrufen oder über die API eine neue festlegen.
Der vollständige Quellcode für main.go ist hier .
Führen Sie das Programm aus:
go run .
Sehr schnell in 15 Minuten skizziert! Sicherlich hat er sich irgendwo geirrt, also werden wir einen Test schreiben und prüfen.
Laden Sie Gonkey herunter und führen Sie es aus:
mkdir -p tests/cases docker run -it -v $(pwd)/tests:/tests lamoda/gonkey -tests tests/cases -host host.docker.internal:8080
Dieser Befehl startet das Image mit gonkey über das Docker, stellt das Verzeichnis tests / case im Container bereit und startet gonkey mit den Parametern -tests tests / case / -host.
Wenn Ihnen der Docker-Ansatz nicht gefällt, besteht eine Alternative zu einem solchen Befehl darin, Folgendes zu schreiben:
go get github.com/lamoda/gonkey go run github.com/lamoda/gonkey -tests tests/cases -host localhost:8080
Gestartet und bekam das Ergebnis:
Failed tests: 0/0
Keine Tests - nichts zu überprüfen. Schreiben wir den ersten Test. Erstellen Sie eine Datei tests / case / light_get.yaml mit dem Mindestinhalt:
- name: WHEN currentLight is requested MUST return red method: GET path: /light/get response: 200: > { "currentLight": "red" }
Auf der ersten Ebene befindet sich eine Liste. Dies bedeutet, dass wir einen Testfall beschrieben haben, aber es können viele davon in der Datei sein. Zusammen bilden sie das Testszenario. Also eine Datei - ein Skript. Sie können mit Testskripten beliebig viele Dateien erstellen und diese gegebenenfalls in Unterverzeichnissen anordnen. Gonkey liest alle yaml- und yml-Dateien aus dem übertragenen Verzeichnis und ist tiefer rekursiv.
In der folgenden Datei werden die Details der Anforderung beschrieben, die an den Server gesendet wird: Methode, Pfad. Noch niedriger ist der Antwortcode (200) und der Antworttext, den wir vom Server erwarten.
Das vollständige Dateiformat ist in README beschrieben .
Wieder ausführen:
docker run -it -v $(pwd)/tests:/tests lamoda/gonkey -tests tests/cases -host host.docker.internal:8080
Ergebnis:
Name: WHEN currentlight is requested MUST return red Request: Method: GET Path: /light/get Query: Body: <no body> Response: Status: 200 OK Body: {} Result: ERRORS! Errors: 1) at path $ values do not match: expected: { "currentLight": "red" } actual: {} Failed tests: 1/1
Fehler! Eine Struktur mit dem Feld currentLight wurde erwartet und eine leere Struktur zurückgegeben. Das ist schlecht. Das erste Problem ist, dass das Ergebnis als Zeichenfolge interpretiert wurde. Dies wird durch die Tatsache angezeigt, dass Gonkey als Problemstelle die gesamte Antwort ohne Details hervorgehoben hat:
expected: { "currentLight": "red" }
Der Grund ist einfach: Ich habe vergessen zu schreiben, damit der Dienst in der Antwort den Inhaltstyp application / json angibt. Wir beheben:
Wir starten den Dienst neu und führen die Tests erneut aus:
Name: WHEN currentlight is requested MUST return red Request: Method: GET Path: /light/get Query: Body: <no body> Response: Status: 200 OK Body: {} Result: ERRORS! Errors: 1) at path $ key is missing: expected: currentLight actual: <missing>
Großartig, es gibt Fortschritte. Gonkey erkennt jetzt die Struktur, aber es ist immer noch falsch: Die Antwort ist leer. Der Grund ist, dass ich in der Typdefinition ein nicht exportierbares Feld currentLight verwendet habe:
In Go wird ein Strukturfeld mit einem Kleinbuchstaben als nicht exportierbar betrachtet, dh von anderen Paketen aus nicht zugänglich. Der JSON-Serializer sieht es nicht und kann es nicht in die Antwort aufnehmen. Wir korrigieren: Wir machen das Feld mit einem Großbuchstaben, was bedeutet, dass es exportiert wird:
Starten Sie den Dienst neu. Führen Sie die Tests erneut aus.
Failed tests: 0/1
Die Tests haben bestanden!
Wir werden ein weiteres Skript schreiben, das die festgelegte Methode testet. Füllen Sie die Datei tests / case / light_set.yaml mit folgendem Inhalt:
- name: WHEN set is requested MUST return no response method: POST path: /light/set request: > { "currentLight": "green" } response: 200: '' - name: WHEN get is requested MUST return green method: GET path: /light/get response: 200: > { "currentLight": "green" }
Der erste Test legt einen neuen Wert für das Verkehrssignal fest und der zweite prüft den Status, um sicherzustellen, dass er sich geändert hat.
Führen Sie die Tests mit demselben Befehl aus:
docker run -it -v $(pwd)/tests:/tests lamoda/gonkey -tests tests/cases -host host.docker.internal:8080
Ergebnis:
Failed tests: 0/3
Ein erfolgreiches Ergebnis, aber wir hatten Glück, dass die Skripte in der von uns benötigten Reihenfolge ausgeführt wurden: zuerst light_get und dann light_set. Was würde passieren, wenn sie das Gegenteil tun würden? Lassen Sie uns umbenennen:
mv tests/cases/light_set.yaml tests/cases/_light_set.yaml
Und wieder laufen:
Errors: 1) at path $.currentLight values do not match: expected: red actual: green Failed tests: 1/3
Zuerst wurde das Set ausgeführt und die Ampel im grünen Zustand belassen, sodass beim nächsten Testlauf ein Fehler festgestellt wurde - er wartete auf Rot.
Eine Möglichkeit, die Tatsache zu beseitigen, dass der Test vom Kontext abhängt, besteht darin, den Dienst am Anfang des Skripts (dh am Anfang der Datei) zu initialisieren, was wir normalerweise im Set-Test tun. Zuerst setzen wir den bekannten Wert, der einen bekannten Effekt erzeugen soll. und überprüfen Sie dann, ob der Effekt einen Effekt hatte.
Eine andere Möglichkeit, den Ausführungskontext vorzubereiten, wenn der Dienst die Datenbank verwendet, besteht darin, Fixtures mit Daten zu verwenden, die zu Beginn des Skripts in die Datenbank geladen werden, wodurch ein vorhersagbarer Status des Dienstes gebildet wird, der überprüft werden kann. Die Beschreibung und Beispiele für die Arbeit mit Fixtures in Gonkey möchte ich in einem separaten Artikel veröffentlichen.
In der Zwischenzeit schlage ich folgende Lösung vor. Da wir im Set-Skript sowohl die Light / Set-Methode als auch die Light / Get-Methode testen, benötigen wir einfach nicht das Light_get-Skript, das kontextsensitiv ist. Ich lösche es und benenne das verbleibende Skript um, damit der Name die Essenz widerspiegelt.
rm tests/cases/light_get.yaml mv tests/cases/_light_set.yaml tests/cases/light_set_get.yaml
Als nächsten Schritt möchte ich einige negative Szenarien für die Arbeit mit unserem Service überprüfen. Funktioniert dies beispielsweise ordnungsgemäß, wenn ich eine falsche Signalfarbe sende? Oder überhaupt keine Farbe senden?
Erstellen Sie ein neues Skript tests / case / light_set_get_negative.yaml:
- name: WHEN set is requested MUST return no response method: POST path: /light/set request: > { "currentLight": "green" } response: 200: '' - name: WHEN incorrect color is passed MUST return error method: POST path: /light/set request: > { "currentLight": "blue" } response: 400: > incorrect current light: blue - name: WHEN color is missing MUST return error method: POST path: /light/set request: > {} response: 400: > incorrect current light: - name: WHEN get is requested MUST have color untouched method: GET path: /light/get response: 200: > { "currentLight": "green" }
Er prüft das:
- Wenn die falsche Farbe übertragen wird, tritt ein Fehler auf.
- Wenn die Farbe nicht übertragen wird, tritt ein Fehler auf.
- Eine falsche Farbübertragung ändert den internen Zustand der Ampel nicht.
Ausführen:
Failed tests: 0/6
Alles in Ordnung ist :)
Gonkey als Bibliothek verbinden
Wie Sie bemerkt haben, testen wir die Service-API vollständig von der Sprache und den Technologien, in denen sie geschrieben ist. Auf die gleiche Weise könnten wir jede öffentliche API testen, für die wir keinen Zugriff auf die Quellcodes haben - es reicht aus, Anfragen zu senden und Antworten zu erhalten.
Für unsere eigenen Anwendungen, die in go geschrieben wurden, gibt es eine bequemere Möglichkeit, gonkey auszuführen - es als Bibliothek mit dem Projekt zu verbinden. Auf diese Weise können Sie den Test ausführen, ohne vorher etwas zu kompilieren - weder Gonkey noch das Projekt selbst -, indem Sie einfach go test
ausführen.
Mit diesem Ansatz scheinen wir einen Komponententest zu schreiben, und im Hauptteil des Tests führen wir Folgendes aus:
- Initialisieren Sie den Webserver auf die gleiche Weise wie beim Start des Dienstes.
- Führen Sie den Testanwendungsserver auf localhost und zufälligem Port aus.
- Wir rufen die Funktion aus der Gonkey-Bibliothek auf und übergeben ihr die Adresse des Testservers und andere Parameter. Im Folgenden werde ich dies veranschaulichen.
Dazu muss unsere Anwendung ein wenig überarbeitet werden. Der entscheidende Punkt ist, die Erstellung des Servers zu einer separaten Funktion zu machen, da wir diese Funktion jetzt an zwei Stellen benötigen: beim Starten des Dienstes und sogar beim Ausführen der Gonkey-Tests.
Ich habe den folgenden Code in eine separate Funktion eingefügt:
func initServer() {
Die Hauptfunktion lautet dann wie folgt:
func main() { initServer()
Die geänderte Haupt-Go-Datei ist vollständig .
Dies hat unsere Hände befreit, also fangen wir an, einen Test zu schreiben. Ich erstelle eine func_test.go-Datei:
func Test_API(t *testing.T) { initServer() srv := httptest.NewServer(nil) runner.RunWithTesting(t, &runner.RunWithTestingParams{ Server: srv, TestsDir: "tests/cases", }) }
Hier ist die vollständige Datei func_test.go .
Das ist alles! Wir prüfen:
go test ./...
Ergebnis:
ok github.com/lamoda/gonkey/examples/traffic-lights-demo 0.018s
Die Tests sind bestanden. Wenn ich sowohl Unit-Tests als auch Gonkey-Tests habe, werden sie alle zusammen ausgeführt - ganz bequem.
Allure ist ein Testberichtformat, mit dem die Ergebnisse klar und schön angezeigt werden können. Gonkey kann Testergebnisse in diesem Format aufzeichnen. Allure zu aktivieren ist sehr einfach:
docker run -it -v $(pwd)/tests:/tests -w /tests lamoda/gonkey -tests cases/ -host host.docker.internal:8080 -allure
Der Bericht wird im Unterverzeichnis allure-results des aktuellen Arbeitsverzeichnisses abgelegt (deshalb habe ich -w / tests angegeben).
Wenn Sie gonkey als Bibliothek verbinden, wird der Allure-Bericht durch Festlegen einer zusätzlichen Umgebungsvariablen GONKEY_ALLURE_DIR aktiviert:
GONKEY_ALLURE_DIR="tests/allure-results" go test ./…
Die in Dateien aufgezeichneten Testergebnisse werden durch die folgenden Befehle in einen interaktiven Bericht konvertiert:
allure generate allure serve
Wie der Bericht aussieht:

Fazit
In den folgenden Artikeln werde ich mich mit der Verwendung von Fixtures in Gonkey und der Nachahmung der Reaktionen anderer Dienste unter Verwendung von Mocks befassen.
Ich lade Sie ein , gonkey in Ihren Projekten auszuprobieren , an seiner Entwicklung teilzunehmen ( Poolanfragen sind willkommen!) Oder es mit einem Sternchen auf dem Github zu markieren, wenn dieses Projekt für Sie in Zukunft nützlich sein kann.