it-swarm.com.de

Trennen von Unit-Tests und Integrationstests in Go

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?

61
Craig Jones

@ 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.

98
Alex

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).

39
Ainar-G

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. 

Einheiten- und Integrationstests prüfen die Harmonie in derselben Datei

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:

  1. mit Integration im Testnamen.
  2. Überprüfen, ob unter der -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."

Nur Unit-Tests ausführen:

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

Nur Integrationstests ausführen:

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.

26
eduncan911

Ich habe vor kurzem versucht, eine Lösung für dasselbe zu finden ... Dies waren meine Kriterien:

  • Die Lösung muss universell sein
  • Kein separates Paket für Integrationstests
  • Die Trennung sollte abgeschlossen sein (ich sollte in der Lage sein, Integrationstests auszuführen nur)
  • Keine spezielle Namenskonvention für Integrationstests
  • Es sollte ohne zusätzliche Werkzeuge gut funktionieren

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.

Verwendungszweck

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

1
mark.sagikazar