Come posso eseguire la configurazione del test utilizzando il pacchetto di test in Go


111

Come posso eseguire l'elaborazione complessiva della configurazione del test che pone le basi per tutti i test quando si utilizza il pacchetto di test ?

Ad esempio in Nunit c'è un [SetUp]attributo.

[TestFixture]
public class SuccessTests
{
  [SetUp] public void Init()
  { /* Load test data */ }
}

2
A partire dalla 1.4 puoi avere un setup globale e smontaggio
Salvador Dali

Risposte:


159

A partire da Go 1.4 è possibile implementare il setup / smontaggio (non è necessario copiare le funzioni prima / dopo ogni test). La documentazione è delineata qui nella sezione Principale :

TestMain viene eseguito nella goroutine principale e può eseguire qualsiasi configurazione e smontaggio necessario per una chiamata a m.Run. Dovrebbe quindi chiamare os.Exit con il risultato di m.Run

Mi ci è voluto del tempo per capire che questo significa che se un test contiene una funzione, func TestMain(m *testing.M)questa verrà chiamata invece di eseguire il test. E in questa funzione posso definire come verranno eseguiti i test. Ad esempio, posso implementare la configurazione globale e lo smontaggio:

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

Un paio di altri esempi possono essere trovati qui .

La funzionalità TestMain aggiunta al framework di test di Go nell'ultima versione è una soluzione semplice per diversi casi d'uso di test. TestMain fornisce un hook globale per eseguire l'installazione e l'arresto, controllare l'ambiente di test, eseguire codice diverso in un processo figlio o verificare la presenza di risorse trapelate dal codice di test. La maggior parte dei pacchetti non avrà bisogno di un TestMain, ma è una gradita aggiunta per quelle volte in cui è necessario.


17
TestMainè una volta in un pacchetto, quindi non è così utile. Trovo che i test secondari siano migliori per scopi più complessi.
Inanc Gumus

3
Come dovresti passare il contesto dalla funzione di configurazione ai test senza utilizzare variabili globali? Ad esempio, se mySetupFunction () crea una directory temporanea in cui eseguire i test (con un nome univoco e casuale), come fanno i test a conoscere il nome della directory? Ci deve essere un posto per impostare questo contesto ??
Lqueryvg

1
Sembra che questo sia il modo ufficiale per gestire gli hook prima e dopo i test, vedere golang.org/pkg/testing/#hdr-Main per la documentazione ufficiale
de-jcup

4
@InancGumuslstat $GOROOT/subtests: no such file or directory
030

1
si prega di notare che 'code: = m.Run ()' è quello che esegue altre TestFunctions!
Alex Punnen

49

Ciò può essere ottenuto inserendo una init()funzione nel _test.gofile. Questo verrà eseguito prima della init()funzione.

// package_test.go
package main

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

_Test.init () verrà chiamato prima della funzione init () del pacchetto.


2
So che stai rispondendo alla tua stessa domanda, quindi questo probabilmente soddisfa il tuo caso d'uso, ma questo non è equivalente all'esempio di NUnit che hai incluso nella tua domanda.
James Henstridge

Bene @ James, ho mostrato un pensiero sulla strada per rispondere al problema e altri hanno già fornito alcuni buoni spunti, incluso il tuo. È utile ottenere influenze esterne per sintonizzare l'approccio. Grazie.
miltonb

2
Giusto. Quello che hai mostrato in questa risposta è un po 'più vicino all'uso [TestFixtureSetUp]dell'attributo di NUnit invece.
James Henstridge

2
non include la parte
smontata

7
Questa non è una buona soluzione se il file di test è nello stesso pacchetto con la funzione principale.
MouseWanted

28

Data una semplice funzione per unit test:

package math

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

Puoi testarlo con una funzione di configurazione che restituisce la funzione di smontaggio. E dopo aver chiamato setup () puoi fare una chiamata differita a teardown ().

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

Lo strumento di test Go riporterà le istruzioni di registrazione nella console della shell:

% 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
% 

È possibile passare alcuni parametri aggiuntivi alla configurazione / smontaggio con questo approccio.


2
Questo è un vero trucco semplice ma efficace. Ottimo uso della sintassi Go.
miltonb

1
Sì, ma aumenta l'annidamento (una specie di piramide del destino in javascript ). Inoltre, i test non vengono eseguiti automaticamente dalla suite come nei test esterni.
Inanc Gumus

12

In genere, i test in go non sono scritti nello stesso stile delle altre lingue. Spesso, ci sono relativamente meno funzioni di test, ma ognuna contiene un set di casi di test basato su tabella. Vedi questo articolo scritto da uno dei membri del team Go.

Con un test basato su tabella, inserisci semplicemente qualsiasi codice di configurazione prima del ciclo che esegue i singoli casi di test specificati nella tabella e successivamente inserisci qualsiasi codice di pulizia.

Se hai ancora codice di configurazione condiviso tra le funzioni di test, puoi estrarre il codice di configurazione condiviso in una funzione e utilizzare a sync.Oncese è importante che venga eseguito esattamente una volta (o come suggerisce un'altra risposta, usa init(), ma questo ha lo svantaggio che l'installazione verrà eseguito anche se i casi di test non vengono eseguiti (forse perché hai limitato i casi di test utilizzando go test -run <regexp>.)

Direi che se pensi di aver bisogno di una configurazione condivisa tra diversi test che vengono eseguiti esattamente una volta, dovresti pensare se ne hai davvero bisogno e se un test guidato da tabella non sarebbe migliore.


6
È fantastico quando si testano cose banali come un analizzatore di flag o un algoritmo che agita i numeri. Ma non aiuta davvero quando si prova a testare diverse funzionalità che richiedono tutte un codice boilerplate simile. Suppongo di poter definire le mie funzioni di test in un array e iterare su quelle, ma poi non è realmente guidato da una tabella tanto quanto un semplice ciclo che dovrebbe essere costruito nel framework di test stesso (sotto forma di una suite di test adeguata con funzioni di configurazione / smontaggio)
iamtheddrman

9

Il framework di test Go non ha nulla di equivalente all'attributo SetUp di NUnit (contrassegnando una funzione da chiamare prima di ogni test nella suite). Ci sono però alcune opzioni:

  1. Chiama semplicemente la tua SetUpfunzione da ogni test dove è necessaria.

  2. Utilizza un'estensione del framework di test di Go che implementa i paradigmi e i concetti di xUnit. Mi vengono in mente tre opzioni forti:

Ciascuna di queste librerie ti incoraggia a organizzare i tuoi test in suite / dispositivi simili ad altri framework xUnit e chiamerà i metodi di configurazione sul tipo di suite / dispositivo prima di ciascuno dei Test*metodi.


0

Spudorato plug, ho creato https://github.com/houqp/gtest per aiutare a risolvere esattamente questo problema.

Ecco un rapido esempio:

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{})
}

Puoi creare qualsiasi gruppo di test che desideri all'interno di un pacchetto con ciascuno di essi utilizzando un set diverso di routine di installazione / smontaggio.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.