
Integrationstests sind eine der Ebenen
der Testpyramide . Normalerweise benötigen sie mehr Zeit, weil In ihnen ersetzen wir nichts durch Simulationen realer Komponenten. Um die Zeit für solche Tests zu verkürzen, können wir sie parallel ausführen. Hier werde ich speziell über solche Tests für Postgresql sprechen.
Im Idealfall sollte jeder Test unabhängig sein, damit sie sich nicht gegenseitig beeinflussen. Mit anderen Worten, jede Testfunktion hat ihren eigenen Zustand. Dies ist ein gutes Zeichen für parallele Tests. Um meinen persönlichen Datensatz für jede Testfunktion abzurufen, habe ich eine Funktion erstellt, die beim Starten eines Tests eine temporäre Schaltung erstellt, Daten in diese lädt und die Schaltung nach Abschluss des Tests zerstört. Jedes erstellte
Schema enthält einen Hash im Namen, um Namenskonflikte zu vermeiden.
Hilfsfunktion
Beginnen wir mit einer
Hilfsfunktion , um Fehler in den Tests anzuzeigen. Ich habe die Hilfsfunktionen von Ben Johnson (Ben Johnson) übernommen, die mir geholfen haben, ein paar Codezeilen zu speichern und meine Fehler klarer und detaillierter zu machen.
Testdaten
Um den Integrationstest der Datenbank auszuführen, müssen Testdaten bereitgestellt werden. Das Go-Testtool unterstützt das Laden von Testdaten aus Dateien. Erstellen Sie zunächst überspringende Ordner mit dem Namen "Testdaten". Zweitens ändert sich beim Ausführen von "go test" der aktuelle Ordner in den Paketordner. Auf diese Weise können Sie den relativen Pfad zum Ordner testdata verwenden, um den Testdatensatz zu laden.
Erstellen einer Datenbankverbindung für den Test
package database import ( "math/rand" "strconv" "testing" "time" _ "github.com/lib/pq" "database/sql" ) const ( dbPort = 5439 dbUser = "postgres" dbPassword = "postgres" dbName = "test" ) func CreateTestDatabase(t *testing.T) (*sql.DB, string, func()) { connectionString := fmt.Sprintf("port=%d user=%s password=%s dbname=%s sslmode=disable", dbPort, dbUser, dbPassword, dbName) db, dbErr := sql.Open("postgres", connectionString) if dbErr != nil { t.Fatalf("Fail to create database. %s", dbErr.Error()) } rand.Seed(time.Now().UnixNano()) schemaName := "test" + strconv.FormatInt(rand.Int63(), 10) _, err := db.Exec("CREATE SCHEMA " + schemaName) if err != nil { t.Fatalf("Fail to create schema. %s", err.Error()) } return db, schemaName, func() { _, err := db.Exec("DROP SCHEMA " + schemaName + " CASCADE") if err != nil { t.Fatalf("Fail to drop database. %s", err.Error()) } } }
Rufen Sie "CreateTestDatabase" auf, um eine Verbindung zur Testdatenbank herzustellen und ein neues Datenschema für die Tests zu erstellen. Diese Funktion gibt die Datenbankverbindung, den Namen des erstellten Schemas und die Bereinigungsfunktion zum Löschen dieses Schemas zurück. Für einen Test ist es besser, den Test nicht zu bestehen, als einen Fehler an den Anrufer zurückzugeben. (Hinweis: Die Rückgabe der Bereinigungsfunktion basiert auf
Mitchell Hashimotos Advanced Testing with Go Talk. )
Laden Sie den Testdatensatz herunter
Ich habe die ".sql" -Dateien verwendet. Eine (1) SQL enthält Daten für eine (1) Tabelle. Dazu gehört das Erstellen einer Tabelle und das Auffüllen mit Daten. Alle SQL-Dateien werden im Ordner "testdata" gespeichert. Hier ist eine Beispiel-SQL-Datei.
CREATE TABLE book ( title character varying(50), author character varying(50) ); INSERT INTO book VALUES ('First Book','First Author'), ('Second Book','Second Author') ;
Und hier ist der komplizierte Teil. Da jede Funktion in einem eigenen Datenschema ausgeführt wird, können wir in diesen SQL-Dateien nicht einfach eine Abfrage ausführen (schreiben). Wir müssen das Schema vor den Tabellennamen angeben, um eine Tabelle zu erstellen oder Daten in das gewünschte temporäre Schema einzufügen. Zum Beispiel sollte ein CREATE TABLE-Buch ... als CREATE TABLE uniqueschema.book geschrieben werden ... und ein INSERT INTO-Buch ... muss in INSERT INTO uniqueschema.book geändert werden .... Ich habe reguläre Ausdrücke verwendet, um Abfragen vor der Ausführung zu ändern. Hier ist der Download-Code für die Testdaten:
package datalayer import ( "bufio" "fmt" "io" "os" "regexp" "testing" "database/sql" "github.com/Hendra-Huang/databaseintegrationtest/testingutil"
Testerstellung
Vor dem Start jedes Tests wird eine Testdatenbank mit einem eindeutigen Namen für das Schema erstellt und die Ausführung der Bereinigungsfunktion zum Löschen dieses Schemas wird verschoben. Der Schemaname wird im Test in die Anforderung eingefügt. Das Wichtigste bei dieser Implementierung ist, dass die Verbindung zur Datenbank anpassbar sein muss, um die Verbindung von der realen Datenbank zur Verbindung zur Testdatenbank zu ändern. Fügen Sie zu Beginn jeder Testfunktion "t.Parallel ()" hinzu, um der Testumgebung anzuzeigen, dass dieser Test parallel ausgeführt werden muss.
Unten ist der vollständige Code:
Hinweis: Unter "TestGetBooks" gehe ich davon aus, dass die Abfrage 2 Bücher als zurückgibt Ich habe so viel Testdatensatz in "testdata / book.sql" angegeben, obwohl es oben einen Insert-Test gibt. Wenn wir die Schaltung zwischen den beiden Tests nicht teilen, schlägt "TestGetBooks" fehl, weil jetzt 3 Zeilen in der Tabelle, 2 aus dem Test, 1 aus dem Testeinsatz oben. Dies ist der Vorteil von getrennten Schaltkreisen für Tests - ihre Daten sind unabhängig und daher sind die Tests unabhängig voneinander.Das Projektbeispiel habe ich hier
github gepostet. Sie können es in sich selbst kopieren, den Test ausführen und das Ergebnis sehen.
Fazit
Bei meinem Projekt reduziert dieser Ansatz die Testzeit im Vergleich zu sequentiellen Tests um 40–50%. Ein weiterer Vorteil der parallelen Ausführung von Tests besteht darin, dass wir einige Fehler vermeiden können, die auftreten können, wenn eine Anwendung mehrere Wettbewerbsaktionen verarbeitet.
Hab einen schönen Test.
- Bild von
medium.com/kongkow-it-medan/parallel-database-integration-test-on-go-application-8706b150ee2e