Trennen von Unit-Tests und Integrationstests in Go

96

Gibt es eine bewährte Methode zur Trennung von Komponententests und Integrationstests in GoLang (testify)? Ich habe eine Mischung aus Komponententests (die nicht auf externen Ressourcen beruhen und daher sehr schnell ausgeführt werden) und Integrationstests (die auf externen Ressourcen basieren und daher langsamer ausgeführt werden). Ich möchte also steuern können, ob die Integrationstests eingeschlossen werden sollen oder nicht, wenn ich sage go test.

Die einfachste Technik scheint darin zu bestehen, ein Integrationsflag in main zu definieren:

var runIntegrationTests = flag.Bool("integration", false
    , "Run the integration tests (in addition to the unit tests)")

Und dann, um jedem Integrationstest eine if-Anweisung hinzuzufügen:

if !*runIntegrationTests {
    this.T().Skip("To run this test, use: go test -integration")
}

Ist das das Beste, was ich tun kann? Ich habe in der Zeugnisdokumentation nach einer Namenskonvention oder etwas gesucht, das dies für mich bewirkt, aber nichts gefunden. Vermisse ich etwas

Craig Jones
quelle
2
Ich denke, die stdlib verwendet -short, um Tests zu deaktivieren, die das Netzwerk treffen (und andere langwierige Dinge auch). Andernfalls sieht Ihre Lösung in Ordnung aus.
Volker
-short ist eine gute Option, ebenso wie Ihre benutzerdefinierten Build-Flags, aber Ihre Flags müssen nicht in main sein. Wenn Sie var integration = flag.Bool("integration", true, "Enable integration testing.")die Variable als außerhalb einer Funktion definieren, wird die Variable im Paketbereich
angezeigt

Antworten:

154

@ Ainar-G schlägt mehrere großartige Muster vor, um Tests zu trennen.

Diese Go-Vorgehensweise von SoundCloud empfiehlt die Verwendung von Build-Tags ( beschrieben im Abschnitt "Build-Einschränkungen" des Build-Pakets ), um auszuwählen, welche Tests ausgeführt werden sollen:

Schreiben Sie eine Integration_test.go und geben Sie ihr ein Build-Tag für die 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 benötigt Build-Tags wie go build, sodass Sie aufrufen können go test -tags=integration. Außerdem wird ein Paket main synthetisiert, das flag.Parse aufruft, sodass alle deklarierten und sichtbaren Flags verarbeitet werden und für Ihre Tests verfügbar sind.

Als ähnliche Option können Integrationstests auch standardmäßig mithilfe einer Erstellungsbedingung ausgeführt // +build !unitund bei Bedarf durch Ausführen deaktiviert werden go test -tags=unit.

@ adamc Kommentare:

Für alle anderen Personen, die versuchen, Build-Tags zu verwenden, ist es wichtig, dass der // +build testKommentar die erste Zeile in Ihrer Datei ist und dass Sie nach dem Kommentar eine leere Zeile einfügen. Andernfalls -tagsignoriert der Befehl die Anweisung.

Außerdem kann das im Build-Kommentar verwendete Tag keinen Bindestrich enthalten, obwohl Unterstriche zulässig sind. Zum Beispiel // +build unit-testswird nicht funktionieren, während // +build unit_testswird.

Alex
quelle
1
Ich benutze dies seit einiger Zeit und es ist bei weitem der logischste und einfachste Ansatz.
Ory Band
1
Wenn Sie Unit-Tests in demselben Paket haben, müssen Sie // + build unitin Unit-Tests festgelegt und -tag Unit verwenden, um die Tests
auszuführen
2
@ Tyler.z.yang können Sie einen Link oder weitere Details zur Ablehnung von Tags bereitstellen? Ich habe solche Informationen nicht gefunden. Ich verwende Tags mit go1.8 für die in der Antwort beschriebene Weise und auch zum Verspotten von Typen und Funktionen in Tests. Es ist eine gute Alternative zu Schnittstellen, denke ich.
Alexander I. Grafov
2
Für alle anderen Benutzer, die versuchen, Build-Tags zu verwenden, ist es wichtig, dass der // +buildTestkommentar die erste Zeile in Ihrer Datei ist und dass Sie nach dem Kommentar eine leere Zeile einfügen. Andernfalls -tagsignoriert der Befehl die Anweisung. Außerdem kann das im Build-Kommentar verwendete Tag keinen Bindestrich enthalten, obwohl Unterstriche zulässig sind. Zum Beispiel // +build unit-testswird nicht funktionieren, während // +build unit_testswird
Adamc
6
Wie gehe ich mit Platzhaltern um? go test -tags=integration ./...funktioniert nicht, es ignoriert das Tag
Erika Dsouza
52

Um näher auf meinem Kommentar zu @ Ainar-G ausgezeichnete Antwort, im vergangenen Jahr habe ich die Kombination der Verwendung -shortmit IntegrationNamenskonvention das Beste aus beiden Welten zu erreichen.

Unit und Integration testen die Harmonie in derselben Datei

Build - Flags hat mich gezwungen , die zuvor mehrere Dateien zu haben ( services_test.go, services_integration_test.gousw.).

Nehmen Sie stattdessen das folgende Beispiel, in dem die ersten beiden Unit-Tests 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 die Konvention hat:

  1. Verwenden Sie Integrationim Testnamen.
  2. Überprüfen, ob unter der -shortFlag-Direktive ausgeführt wird.

Grundsätzlich lautet die Spezifikation: "Schreiben Sie alle Tests normal. Wenn es sich um Langzeittests oder Integrationstests handelt, befolgen Sie diese Namenskonvention und prüfen Sie, ob Sie -shortIhren Kollegen gegenüber nett sind."

Nur Unit-Tests ausführen:

go test -v -short

Dies bietet Ihnen eine schöne Reihe von Nachrichten wie:

=== RUN   TestPostgresVersionIntegration
--- SKIP: TestPostgresVersionIntegration (0.00s)
        service_test.go:138: skipping integration test

Führen Sie nur Integrationstests aus:

go test -run Integration

Dies führt nur die Integrationstests aus. Nützlich für die Rauchprüfung von Kanarienvögeln in der Produktion.

Offensichtlich ist der Nachteil dieses Ansatzes, dass, wenn jemand go testohne das -shortFlag ausgeführt wird, standardmäßig alle Tests ausgeführt werden - Einheiten- und Integrationstests.

Wenn Ihr Projekt groß genug ist, um Unit- und Integrationstests durchzuführen, verwenden Sie in der Realität höchstwahrscheinlich eine, Makefilein der Sie einfache Anweisungen verwenden go test -shortkönnen. Oder legen Sie es einfach in Ihre README.mdDatei und nennen Sie es den Tag.

eduncan911
quelle
3
liebe die Einfachheit
Jacob Stanley
Erstellen Sie ein separates Paket für einen solchen Test, um nur auf die öffentlichen Teile des Pakets zuzugreifen? Oder alles gemischt?
Dr.eel
@ Dr.eel Nun, das ist OT aus der Antwort. Aber ich persönlich bevorzuge beides: einen anderen Paketnamen für die Tests, damit ich importmein Paket testen und dagegen testen kann, was mir letztendlich zeigt, wie meine API für andere aussieht. Ich folge dann jeder verbleibenden Logik, die als interne Testpaketnamen behandelt werden muss.
eduncan911
@ eduncan911 Danke für die Antwort! Soweit ich package servicesweiß, enthält hier ein Integrationstest. Um die API für das Paket als Blackbox zu testen, sollten wir es anders benennen, damit wir package services_integration_testnicht mit internen Strukturen arbeiten können. Daher sollte das Paket für Komponententests (Zugriff auf Interna) benannt werden package services. Ist es so?
Dr.eel
Das stimmt ja. Hier ist ein sauberes Beispiel dafür, wie ich es mache: github.com/eduncan911/podcast (100% Codeabdeckung anhand von Beispielen
beachten
50

Ich sehe drei mögliche Lösungen. Der erste besteht darin, den Kurzmodus für Komponententests zu verwenden. Sie würden also go test -shortmit Komponententests und demselben verwenden, jedoch ohne das -shortFlag, um auch Ihre Integrationstests auszuführen. Die Standardbibliothek verwendet den Kurzmodus, um entweder lange laufende Tests zu überspringen oder sie durch die Bereitstellung einfacherer Daten schneller auszuführen.

Die zweite besteht darin, eine Konvention zu verwenden und Ihre Tests entweder TestUnitFoooder aufzurufen TestIntegrationFoound dann das -runTestflag zu verwenden, um anzugeben, welche Tests ausgeführt werden sollen. Sie würden also go test -run 'Unit'für Unit-Tests und go test -run 'Integration'für Integrationstests verwenden.

Die dritte Option besteht darin, eine Umgebungsvariable zu verwenden und sie in Ihrem Test-Setup mit abzurufen os.Getenv. Dann würden Sie einfach go testfür Unit-Tests und FOO_TEST_INTEGRATION=true go testfür Integrationstests verwenden.

Ich persönlich würde das vorziehen -short Lösung , da sie einfacher ist und in der Standardbibliothek verwendet wird. Es scheint also eine de facto Möglichkeit zu sein, lang laufende Tests zu trennen / zu vereinfachen. Die -runund os.Getenv-Lösungen bieten jedoch mehr Flexibilität (auch Vorsicht ist geboten, da es sich um reguläre Ausdrücke handelt -run).

Ainar-G
quelle
1
Beachten Sie, dass Community-Testläufer (z. B. Tester-Go), die IDEs (Atom, Sublime usw.) gemeinsam sind, die integrierte Option haben -short, zusammen mit -coverageund anderen mit Flag ausgeführt zu werden . Daher verwende ich eine Kombination aus beiden Integrationen im Testnamen und if testing.Short()Überprüfungen innerhalb dieser Tests. es ermöglicht mir, das Beste aus beiden Welten zu haben: -shortinnerhalb von IDEs ausführen und explizit nur Integrationstests mitgo test -run "Integration"
eduncan911
5

Ich habe kürzlich versucht, eine Lösung dafür zu finden. Das waren meine Kriterien:

  • Die Lösung muss universell sein
  • Kein separates Paket für Integrationstests
  • Die Trennung sollte vollständig sein (ich soll Integrationstests ausgeführt werden kann , um nur )
  • Keine spezielle Namenskonvention für Integrationstests
  • Es sollte ohne zusätzliches Werkzeug gut funktionieren

Die oben genannten Lösungen (benutzerdefiniertes Flag, benutzerdefiniertes Build-Tag, Umgebungsvariablen) erfüllten nicht alle oben genannten Kriterien. Nach einigem Graben und Spielen kam ich auf diese 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 darin bestehen, den Code in eine Hilfsfunktion zu exportieren.

Verwendung

Führen Sie Integrationstests nur für alle Pakete in einem Projekt aus:

go test -v ./... -run ^TestIntegration$

Führen Sie alle Tests aus ( regulär und Integration):

go test -v ./... -run .\*

Führen Sie nur regelmäßige Tests durch:

go test -v ./...

Diese Lösung funktioniert gut ohne Werkzeuge, aber ein Makefile oder einige Aliase können es dem Benutzer leichter machen. Es kann auch problemlos in jede IDE integriert werden, die das Ausführen von Go-Tests unterstützt.

Das vollständige Beispiel finden Sie hier: https://github.com/sagikazarmark/modern-go-application

mark.sagikazar
quelle