Gibt es eine bewährte Methode, um Unit-Tests und Integrationstests in GoLang (testify) zu trennen? Ich habe eine Mischung aus Komponententests (die keine externen Ressourcen verwenden und daher sehr schnell laufen) und Integrationstests (die externe Ressourcen verwenden und daher langsamer laufen). Ich möchte also in der Lage sein zu steuern, ob die Integrationstests eingeschlossen werden sollen, wenn ich go test
sage.
Die einfachste Technik scheint die Definition einer -integrate-Flagge in main zu sein:
var runIntegrationTests = flag.Bool("integration", false
, "Run the integration tests (in addition to the unit tests)")
Und fügen Sie dann bei jedem Integrationstest eine if-Anweisung hinzu:
if !*runIntegrationTests {
this.T().Skip("To run this test, use: go test -integration")
}
Ist das das Beste, was ich tun kann? Ich suchte in der Zeugnisdokumentation nach, ob es vielleicht eine Namenskonvention gibt oder etwas, das dies für mich bewirkt, aber nichts gefunden hat. Fehlt mir etwas?
@ Ainar-G schlägt verschiedene Muster vor, um Tests zu trennen.
Diese Reihe von Go-Verfahren von SoundCloud empfiehlt die Verwendung von Build-Tags ( beschrieben im Abschnitt "Build Constraints" des Build-Pakets ), um die auszuführenden Tests auszuwählen:
Schreiben Sie eine integration_test.go und geben Sie ihm ein Build-Tag der Integration. Definieren Sie (globale) Flags für Dinge wie Dienstadressen und Verbindungszeichenfolgen, und verwenden Sie sie in Ihren Tests.
// +build integration var fooAddr = flag.String(...) func TestToo(t *testing.T) { f, err := foo.Connect(*fooAddr) // ... }
go test nimmt Build-Tags wie go build auf, sodass Sie
go test -tags=integration
aufrufen können. Es synthetisiert auch ein Paket main, das flag.Parse aufruft, sodass alle deklarierten und sichtbaren Flags verarbeitet und für Ihre Tests verfügbar sind.
Als ähnliche Option können Sie auch Integrationstests standardmäßig ausführen, indem Sie die Build-Bedingung // +build !unit
verwenden, und sie bei Bedarf durch Ausführen von go test -tags=unit
deaktivieren.
@adamc Kommentare:
Für alle anderen Benutzer, die versuchen, Build-Tags zu verwenden, ist es wichtig, dass der // +build test
-Kommentar die erste Zeile in Ihrer Datei ist und dass Sie nach dem Kommentar eine leere Zeile einfügen. Andernfalls ignoriert der -tags
-Befehl die Direktive.
Das im Erstellungskommentar verwendete Tag darf auch keinen Bindestrich enthalten, obwohl Unterstriche zulässig sind. Zum Beispiel wird // +build unit-tests
nicht funktionieren, wohingegen // +build unit_tests
dies tun wird.
Ich sehe drei mögliche Lösungen. Der erste ist der short-Modus für Komponententests. Sie würden also go test -short
mit Komponententests und denselben, aber ohne das -short
-Flag verwenden, um Ihre Integrationstests ebenfalls auszuführen. Die Standardbibliothek verwendet den Kurzmodus, um lang laufende Tests zu überspringen oder durch einfachere Daten schneller zu laufen.
Die zweite besteht darin, eine Konvention zu verwenden und Ihre Tests entweder TestUnitFoo
oder TestIntegrationFoo
aufzurufen und dann mit dem -run
-Testflag anzugeben, welche Tests ausgeführt werden sollen. Sie würden also go test -run 'Unit'
für Komponententests und go test -run 'Integration'
für Integrationstests verwenden.
Die dritte Option besteht darin, eine Umgebungsvariable zu verwenden und sie mit os.Getenv
in Ihre Testkonfiguration zu holen. Dann würden Sie einfachen go test
für Komponententests und FOO_TEST_INTEGRATION=true go test
für Integrationstests verwenden.
Ich persönlich würde die -short
-Lösung vorziehen, da sie einfacher ist und in der Standardbibliothek verwendet wird. Es scheint also eine De-facto-Methode zur Trennung/Vereinfachung von Tests mit langer Laufzeit. Die Lösungen -run
und os.Getenv
bieten jedoch mehr Flexibilität (mehr Vorsicht ist auch geboten, da Regexx -run
beteiligt ist).
Um auf meinen Kommentar zu @ Ainar-Gs hervorragender Antwort einzugehen, habe ich im letzten Jahr die Kombination aus -short
mit Integration
Namenskonvention verwendet, um das Beste aus beiden Welten zu erreichen.
Die Erstellung von Flags zwang mich zuvor, mehrere Dateien zu haben (services_test.go
, services_integration_test.go
usw.).
Nehmen Sie stattdessen dieses Beispiel, wo die ersten beiden Komponententests sind und ich am Ende einen Integrationstest habe:
package services
import "testing"
func TestServiceFunc(t *testing.T) {
t.Parallel()
...
}
func TestInvalidServiceFunc3(t *testing.T) {
t.Parallel()
...
}
func TestPostgresVersionIntegration(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
...
}
Beachten Sie, dass der letzte Test folgende Konventionen hat:
Integration
im Testnamen.-short
-Flag-Direktive ausgeführt wird.Grundsätzlich gilt: "Schreiben Sie alle Tests normal. Wenn es sich um Tests mit langer Laufzeit oder um einen Integrationstest handelt, befolgen Sie diese Namenskonvention und prüfen Sie, ob -short
für Ihre Kollegen nett ist."
go test -v -short
dies bietet Ihnen eine Reihe von Nachrichten wie:
=== RUN TestPostgresVersionIntegration
--- SKIP: TestPostgresVersionIntegration (0.00s)
service_test.go:138: skipping integration test
go test -run Integration
Dadurch werden nur die Integrationstests ausgeführt. Nützlich für die Rauchprüfung von Kanarienvögeln in der Produktion.
Offensichtlich ist der Nachteil dieses Ansatzes, wenn jemand go test
ohne das -short
-Flag ausführt, dass standardmäßig alle Tests ausgeführt werden - Einheiten- und Integrationstests.
Wenn Ihr Projekt groß genug ist, um Unit- und Integrationstests durchzuführen, verwenden Sie wahrscheinlich Makefile
, in dem Sie einfache Anweisungen zur Verwendung von go test -short
haben können. Oder fügen Sie es einfach in Ihre README.md
-Datei ein und nennen Sie es den Tag.
Ich habe vor kurzem versucht, eine Lösung für dasselbe zu finden ... Dies waren meine Kriterien:
Die oben genannten Lösungen (benutzerdefiniertes Flag, benutzerdefiniertes Build-Tag, Umgebungsvariablen) erfüllten nicht alle oben genannten Kriterien. Nach ein wenig Graben und Spielen kam ich zu dieser Lösung:
package main
import (
"flag"
"regexp"
"testing"
)
func TestIntegration(t *testing.T) {
if m := flag.Lookup("test.run").Value.String(); m == "" || !regexp.MustCompile(m).MatchString(t.Name()) {
t.Skip("skipping as execution was not requested explicitly using go test -run")
}
t.Parallel()
t.Run("HelloWorld", testHelloWorld)
t.Run("SayHello", testSayHello)
}
Die Implementierung ist unkompliziert und minimal. Es erfordert zwar eine einfache Konvention für Tests, ist aber weniger fehleranfällig. Eine weitere Verbesserung könnte der Export des Codes in eine Hilfsfunktion sein.
Führen Sie Integrationstests nur für alle Pakete in einem Projekt aus:
go test -v ./... -run ^TestIntegration$
Führen Sie alle Tests aus (regular und Integration):
go test -v ./... -run .\*
Nur regular -Tests ausführen:
go test -v ./...
Diese Lösung funktioniert gut ohne Werkzeug, aber ein Makefile oder einige Aliasnamen können den Benutzer einfacher machen. Es kann auch problemlos in alle IDE integriert werden, die das Ausführen von Go-Tests unterstützen.
Das vollständige Beispiel finden Sie hier: https://github.com/sagikazarmark/modern-go-application