Wie kann ich das Test-Setup mit dem Testpaket in Go durchführen?

110

Wie kann ich die gesamte Test-Setup-Verarbeitung durchführen, die die Grundlage für alle Tests bei Verwendung des Testpakets bildet ?

Als Beispiel in Nunit gibt es ein [SetUp]Attribut.

[TestFixture]
public class SuccessTests
{
  [SetUp] public void Init()
  { /* Load test data */ }
}
miltonb
quelle
2
Ab 1.4 können Sie ein globales Setup und Teardown durchführen
Salvador Dali

Antworten:

156

Ab Go 1.4 können Sie Setup / Teardown implementieren (Sie müssen Ihre Funktionen nicht vor / nach jedem Test kopieren). Die Dokumentation wird skizziert hier im Hauptteil:

TestMain wird in der Haupt-Goroutine ausgeführt und kann alle erforderlichen Einstellungen und Abbrüche für einen Anruf bei m.Run vornehmen. Es sollte dann os.Exit mit dem Ergebnis von m.Run aufrufen

Ich habe einige Zeit gebraucht, um herauszufinden, dass dies bedeutet, dass wenn ein Test eine Funktion enthält, func TestMain(m *testing.M)diese Funktion aufgerufen wird, anstatt den Test auszuführen . Und in dieser Funktion kann ich definieren, wie die Tests ausgeführt werden. Zum Beispiel kann ich globales Setup und Teardown implementieren:

func TestMain(m *testing.M) {
    setup()
    code := m.Run() 
    shutdown()
    os.Exit(code)
}

Einige andere Beispiele finden Sie hier .

Die TestMain-Funktion, die in der neuesten Version zum Testframework von Go hinzugefügt wurde, ist eine einfache Lösung für mehrere Testanwendungsfälle. TestMain bietet einen globalen Hook zum Einrichten und Herunterfahren, Steuern der Testumgebung, Ausführen von unterschiedlichem Code in einem untergeordneten Prozess oder Überprüfen auf Ressourcen, die durch Testcode verloren gehen. Die meisten Pakete benötigen kein TestMain, aber es ist eine willkommene Ergänzung für die Zeiten, in denen es benötigt wird.

Salvador Dali
quelle
16
TestMainist einmal in einem Paket, also ist es nicht so nützlich. Ich finde, Untertests sind besser für komplexere Zwecke.
Inanc Gumus
3
Wie sollen Sie den Kontext von der Setup-Funktion an die Tests übergeben, ohne globale Variablen zu verwenden? Wenn mySetupFunction () beispielsweise ein temporäres Verzeichnis erstellt, in dem Tests durchgeführt werden sollen (mit einem eindeutigen, zufälligen Namen), woher kennen die Tests den Namen des Verzeichnisses? Es muss einen Ort geben, an dem dieser Kontext festgelegt werden kann?
Lqueryvg
1
Es scheint, dass dies eine offizielle Methode ist, um vor und nach Hooks für Tests umzugehen. Siehe golang.org/pkg/testing/#hdr-Main für die offizielle Dokumentation
de-jcup
4
@InancGumuslstat $GOROOT/subtests: no such file or directory
030
1
Bitte beachten Sie, dass 'code: = m.Run ()' derjenige ist, der andere TestFunctions ausführt!
Alex Punnen
49

Dies kann erreicht werden, indem eine init()Funktion in die _test.goDatei eingefügt wird. Dies wird vor der init()Funktion ausgeführt.

// package_test.go
package main

func init() {
     /* load test data */
}

Die Datei _test.init () wird vor der Funktion package init () aufgerufen.

miltonb
quelle
2
Ich weiß, dass Sie Ihre eigene Frage beantworten, sodass dies wahrscheinlich Ihren eigenen Anwendungsfall erfüllt, aber dies entspricht nicht dem NUnit-Beispiel, das Sie in Ihre Frage aufgenommen haben.
James Henstridge
Nun, @james, ich habe einen Gedanken zur Beantwortung des Problems gezeigt und andere haben bereits einige gute Erkenntnisse geliefert, einschließlich Ihrer. Es ist nützlich, äußere Einflüsse zu bekommen, um den eigenen Ansatz abzustimmen. Vielen Dank.
Miltonb
2
Meinetwegen. Was Sie in dieser Antwort gezeigt haben, ist der Verwendung von NUnit etwas näher[TestFixtureSetUp] Attributs .
James Henstridge
2
Es enthält keinen Abreißteil
Abreißteil
7
Dies ist keine gute Lösung, wenn sich Ihre Testdatei im selben Paket mit der Hauptfunktion befindet.
MouseWanted
28

Angesichts einer einfachen Funktion zum Unit-Test:

package math

func Sum(a, b int) int {
    return a + b
}

Sie können es mit einer Setup-Funktion testen, die die Teardown-Funktion zurückgibt. Und nach dem Aufruf von setup () können Sie einen verzögerten Aufruf von teardown () durchführen.

package math

import "testing"

func setupTestCase(t *testing.T) func(t *testing.T) {
    t.Log("setup test case")
    return func(t *testing.T) {
        t.Log("teardown test case")
    }
}

func setupSubTest(t *testing.T) func(t *testing.T) {
    t.Log("setup sub test")
    return func(t *testing.T) {
        t.Log("teardown sub test")
    }
}

func TestAddition(t *testing.T) {
    cases := []struct {
        name     string
        a        int
        b        int
        expected int
    }{
        {"add", 2, 2, 4},
        {"minus", 0, -2, -2},
        {"zero", 0, 0, 0},
    }

    teardownTestCase := setupTestCase(t)
    defer teardownTestCase(t)

    for _, tc := range cases {
        t.Run(tc.name, func(t *testing.T) {
            teardownSubTest := setupSubTest(t)
            defer teardownSubTest(t)

            result := Sum(tc.a, tc.b)
            if result != tc.expected {
                t.Fatalf("expected sum %v, but got %v", tc.expected, result)
            }
        })
    }
}

Das Go-Test-Tool meldet die Protokollierungsanweisungen in der Shell-Konsole:

% go test -v
=== RUN   TestAddition
=== RUN   TestAddition/add
=== RUN   TestAddition/minus
=== RUN   TestAddition/zero
--- PASS: TestAddition (0.00s)
    math_test.go:6: setup test case
    --- PASS: TestAddition/add (0.00s)
        math_test.go:13: setup sub test
        math_test.go:15: teardown sub test
    --- PASS: TestAddition/minus (0.00s)
        math_test.go:13: setup sub test
        math_test.go:15: teardown sub test
    --- PASS: TestAddition/zero (0.00s)
        math_test.go:13: setup sub test
        math_test.go:15: teardown sub test
    math_test.go:8: teardown test case
PASS
ok      github.com/kare/go-unit-test-setup-teardown 0.010s
% 

Mit diesem Ansatz können Sie einige zusätzliche Parameter an Setup / Teardown übergeben.

Kare Nuorteva
quelle
2
Das ist ein wirklich einfacher, aber effektiver Trick. Gute Verwendung der Go-Syntax.
Miltonb
1
Ja, aber es erhöht die Verschachtelung (eine Art Pyramide des Untergangs in Javascript ). Und Tests werden von der Suite nicht automatisch ausgeführt, wie in den äußeren Tests.
Inanc Gumus
12

In der Regel werden Tests in go nicht im gleichen Stil wie in anderen Sprachen geschrieben. Oft gibt es relativ weniger Testfunktionen, aber jede enthält eine tabellengesteuerte Reihe von Testfällen. Sehen diesen Artikel, der von einem der Go-Teams geschrieben wurde.

Bei einem tabellengesteuerten Test setzen Sie einfach einen beliebigen Setup-Code vor die Schleife, die die einzelnen in der Tabelle angegebenen Testfälle ausführt, und fügen anschließend einen Bereinigungscode ein.

Wenn Sie noch gemeinsam genutzten Setup-Code zwischen Testfunktionen haben, können Sie den gemeinsam genutzten Setup-Code in eine Funktion extrahieren und a verwenden, sync.Oncewenn es wichtig ist, dass er genau einmal ausgeführt wird (oder, wie eine andere Antwort vorschlägt, verwenden init(), dies hat jedoch den Nachteil, dass das Setup wird auch dann ausgeführt, wenn die Testfälle nicht ausgeführt werden (möglicherweise, weil Sie die Testfälle mithilfe von eingeschränkt haben go test -run <regexp>.)

Ich würde sagen, wenn Sie der Meinung sind, dass Sie ein gemeinsames Setup zwischen verschiedenen Tests benötigen, das genau einmal ausgeführt wird, sollten Sie überlegen, ob Sie es wirklich benötigen und ob ein tabellengesteuerter Test nicht besser wäre.

Paul Hankin
quelle
6
Das ist großartig, wenn Sie triviale Dinge wie einen Flag-Parser oder einen Algorithmus testen, der Zahlen verarbeitet. Es ist jedoch nicht wirklich hilfreich, wenn Sie versuchen, verschiedene Funktionen zu testen, für die alle einen ähnlichen Code erforderlich sind. Ich nehme an, ich könnte meine Testfunktionen in einem Array definieren und über diese iterieren, aber dann ist es nicht wirklich tabellengesteuert, sondern eine einfache Schleife, die eigentlich nur in das Testframework selbst eingebaut werden sollte (in Form einer richtigen Testsuite) mit Setup / Teardown-Funktionen)
iamtheddrman
9

Das Go-Test-Framework hat nichts, was dem SetUp-Attribut von NUnit entspricht (das eine Funktion markiert, die vor jedem Test in der Suite aufgerufen werden soll). Es gibt jedoch einige Optionen:

  1. Rufen Sie einfach Ihre SetUpFunktion von jedem Test aus auf, wo sie benötigt wird.

  2. Verwenden Sie eine Erweiterung des Testframeworks von Go, die xUnit-Paradigmen und -Konzepte implementiert. Drei starke Optionen kommen in den Sinn:

Jede dieser Bibliotheken empfiehlt Ihnen, Ihre Tests in Suites / Fixtures zu organisieren, die anderen xUnit-Frameworks ähneln, und ruft die Setup-Methoden für den Suite- / Fixture-Typ vor jeder der Test*Methoden auf.

James Henstridge
quelle
0

Schamloser Plug, ich habe https://github.com/houqp/gtest erstellt , um genau dieses Problem zu lösen.

Hier ist ein kurzes Beispiel:

import (
  "strings"
  "testing"
  "github.com/houqp/gtest"
)

type SampleTests struct{}

// Setup and Teardown are invoked per test group run
func (s *SampleTests) Setup(t *testing.T)      {}
func (s *SampleTests) Teardown(t *testing.T)   {}
// BeforeEach and AfterEach are invoked per test run
func (s *SampleTests) BeforeEach(t *testing.T) {}
func (s *SampleTests) AfterEach(t *testing.T)  {}

func (s *SampleTests) SubTestCompare(t *testing.T) {
  if 1 != 1 {
    t.FailNow()
  }
}

func (s *SampleTests) SubTestCheckPrefix(t *testing.T) {
  if !strings.HasPrefix("abc", "ab") {
    t.FailNow()
  }
}

func TestSampleTests(t *testing.T) {
  gtest.RunSubTests(t, &SampleTests{})
}

Sie können mit jeder von ihnen eine beliebige Testgruppe innerhalb eines Pakets erstellen, indem Sie unterschiedliche Setup- / Teardown-Routinen verwenden.

houqp
quelle