Separazione di unit test e test di integrazione in Go


97

Esiste una best practice consolidata per separare unit test e test di integrazione in GoLang (testimoniare)? Ho un mix di unit test (che non si basano su risorse esterne e quindi funzionano molto velocemente) e test di integrazione (che si basano su risorse esterne e quindi funzionano più lentamente). Quindi, voglio essere in grado di controllare se includere o meno i test di integrazione quando dico go test.

La tecnica più semplice sembrerebbe essere quella di definire un flag -integrate in main:

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

E poi per aggiungere un'istruzione if all'inizio di ogni test di integrazione:

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

È questo il meglio che posso fare? Ho cercato nella documentazione di testimoniare per vedere se esiste forse una convenzione di denominazione o qualcosa che lo fa per me, ma non ho trovato nulla. Mi sto perdendo qualcosa?


2
Penso che lo stdlib usi -short per disabilitare i test che colpiscono la rete (e anche altre cose insolite). Altrimenti la tua soluzione sembra a posto.
Volker

-short è una buona opzione, così come i flag di build personalizzati, ma non è necessario che i flag siano in main. se definisci la var come var integration = flag.Bool("integration", true, "Enable integration testing.")esterna a una funzione, la variabile verrà visualizzata nell'ambito del pacchetto e il flag funzionerà correttamente
Atifm

Risposte:


155

@ Ainar-G suggerisce diversi ottimi modelli per separare i test.

Questo insieme di pratiche Go di SoundCloud consiglia di utilizzare i tag build ( descritti nella sezione "Build Constraints" del pacchetto build ) per selezionare i test da eseguire:

Scrivi un integration_test.go e assegnagli un tag build di integrazione. Definisci flag (globali) per cose come indirizzi di servizio e stringhe di connessione e usali nei tuoi test.

// +build integration

var fooAddr = flag.String(...)

func TestToo(t *testing.T) {
    f, err := foo.Connect(*fooAddr)
    // ...
}

go test accetta i tag build proprio come go build, quindi puoi chiamare go test -tags=integration. Sintetizza anche un pacchetto main che chiama flag.Parse, quindi qualsiasi flag dichiarato e visibile verrà elaborato e disponibile per i tuoi test.

Come opzione simile, potresti anche fare in modo che i test di integrazione vengano eseguiti per impostazione predefinita utilizzando una condizione di compilazione // +build !unite quindi disabilitarli su richiesta eseguendoli go test -tags=unit.

@adamc commenti:

Per chiunque altro tenti di utilizzare i tag di compilazione, è importante che il // +build testcommento sia la prima riga del file e che includa una riga vuota dopo il commento, altrimenti il -tagscomando ignorerà la direttiva.

Inoltre, il tag utilizzato nel commento di build non può avere un trattino, sebbene siano consentiti i trattini bassi. Ad esempio, // +build unit-testsnon funzionerà, mentre // +build unit_testsfunzionerà.


1
Lo uso da un po 'di tempo ed è di gran lunga l'approccio più logico e semplice.
Ory Band

1
se hai unit test nello stesso pacchetto, devi impostare // + build unitunit test e utilizzare -tag unit per eseguire i test
LeoCBS

2
@ Tyler.z.yang potete fornire un collegamento o ulteriori dettagli sulla deprecazione dei tag? Non ho trovato tali informazioni. Sto usando i tag con go1.8 per il modo descritto nella risposta e anche per deridere tipi e funzioni nei test. Penso sia una buona alternativa alle interfacce.
Alexander I.Grafov

2
Per chiunque altro tenti di utilizzare i tag di compilazione, è importante che il // +buildcommento di prova sia la prima riga del file e che includa una riga vuota dopo il commento, altrimenti il -tagscomando ignorerà la direttiva. Inoltre, il tag utilizzato nel commento di build non può avere un trattino, sebbene siano consentiti i trattini bassi. Ad esempio, // +build unit-testsnon funziona, mentre // +build unit_testsvolontà
adamc

6
Come gestire i caratteri jolly? go test -tags=integration ./...non funziona, ignora il tag
Erika Dsouza

53

Per elaborare il mio commento all'eccellente risposta di @ Ainar-G, nell'ultimo anno ho utilizzato la combinazione di -shortcon la Integrationconvenzione di denominazione per ottenere il meglio da entrambi i mondi.

Unit and Integration verifica l'armonia, nello stesso file

I flag di build in precedenza mi costringevano ad avere più file (services_test.go , services_integration_test.go, ecc).

Invece, prendi questo esempio di seguito dove i primi due sono unit test e ho un test di integrazione alla fine:

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

Si noti che l'ultimo test ha la convenzione di:

  1. utilizzando Integrationnel nome del test.
  2. controllando se funziona sotto la -shortdirettiva flag.

Fondamentalmente, la specifica è: "scrivi tutti i test normalmente. Se si tratta di un test di lunga durata o di un test di integrazione, segui questa convenzione di denominazione e verifica -shortdi essere gentile con i tuoi colleghi".

Esegui solo unit test:

go test -v -short

questo ti fornisce una bella serie di messaggi come:

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

Esegui solo test di integrazione:

go test -run Integration

Questo esegue solo i test di integrazione. Utile per i canarini in produzione.

Ovviamente lo svantaggio di questo approccio è che se qualcuno corre go testsenza il-short flag, per impostazione predefinita eseguirà tutti i test: test di unità e di integrazione.

In realtà, se il tuo progetto è abbastanza grande da contenere test di unità e di integrazione, molto probabilmente stai usando un in Makefilecui puoi avere semplici direttive da usare go test -shortal suo interno. Oppure mettilo nel tuo README.mdfile e chiamalo il giorno.


3
amo la semplicità
Jacob Stanley,

Crei un pacchetto separato per tale test per accedere solo alle parti pubbliche del pacchetto? O tutto misto?
Dr.eel

@ Dr.eel Bene, questo è OT dalla risposta. Ma personalmente, preferisco entrambi: un nome di pacchetto diverso per i test in modo da poter il importmio pacchetto e testarlo, il che finisce per mostrarmi come appare la mia API agli altri. Seguo quindi la logica rimanente che deve essere coperta come nomi di pacchetti di test interni.
eduncan911

@ eduncan911 Grazie per la risposta! Quindi come ho capito qui package servicescontiene un test di integrazione, quindi per testare l'API del pacchetto come una scatola nera dovremmo chiamarlo in un altro modo package services_integration_testche non ci darà la possibilità di lavorare con le strutture interne. Quindi il pacchetto per gli unit test (accesso agli interni) dovrebbe essere denominato package services. È così?
Dr.eel

È corretto, sì. Ecco un chiaro esempio di come lo faccio: github.com/eduncan911/podcast (nota la copertura del codice del 100%, utilizzando esempi)
eduncan911

50

Vedo tre possibili soluzioni. Il primo è usare la modalità breve per i test unitari. Quindi useresti go test -shortcon i test unitari e lo stesso ma senza l'estensione-short flag per eseguire anche i tuoi test di integrazione. La libreria standard utilizza la modalità breve per saltare i test a esecuzione prolungata o per renderli più veloci fornendo dati più semplici.

Il secondo consiste nell'usare una convenzione e chiamare i test o TestUnitFooo TestIntegrationFooe quindi utilizzare il -runflag di test per indicare quali test eseguire. Quindi lo useresti go test -run 'Unit'per i test unitari e go test -run 'Integration'per i test di integrazione.

La terza opzione è usare una variabile d'ambiente e ottenerla nella configurazione dei test con os.Getenv. Quindi useresti semplice go testper i test unitari e FOO_TEST_INTEGRATION=true go testper i test di integrazione.

Personalmente preferirei la -shortsoluzione poiché è più semplice e viene utilizzata nella libreria standard, quindi sembra che sia un modo di fatto per separare / semplificare i test di lunga durata. Ma le soluzioni -rune os.Getenvoffrono maggiore flessibilità (è necessaria anche maggiore cautela, poiché sono coinvolte le espressioni regolari -run).


1
nota che i test runner della comunità (ad esempio Tester-Go) comuni agli IDE (Atom, Sublime, ecc.) hanno l'opzione incorporata per eseguire con -shortflag, insieme ad -coveragealtri. pertanto, utilizzo una combinazione di entrambi Integration nel nome del test, insieme ai if testing.Short()controlli all'interno di tali test. mi consente di avere il meglio di entrambi i mondi: eseguire -shortall'interno di IDE ed eseguire esplicitamente solo test di integrazione congo test -run "Integration"
eduncan911

5

Recentemente stavo cercando di trovare una soluzione per lo stesso. Questi erano i miei criteri:

  • La soluzione deve essere universale
  • Nessun pacchetto separato per i test di integrazione
  • La separazione dovrebbe essere completa (dovrei essere in grado di eseguire solo test di integrazione )
  • Nessuna convenzione di denominazione speciale per i test di integrazione
  • Dovrebbe funzionare bene senza strumenti aggiuntivi

Le soluzioni di cui sopra (flag personalizzato, tag di build personalizzato, variabili di ambiente) non soddisfacevano realmente tutti i criteri di cui sopra, quindi dopo un po 'di ricerca e di gioco ho trovato questa soluzione:

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

L'implementazione è semplice e minima. Sebbene richieda una semplice convenzione per i test, ma è meno soggetto a errori. Un ulteriore miglioramento potrebbe essere l'esportazione del codice in una funzione di supporto.

Utilizzo

Esegui test di integrazione solo in tutti i pacchetti in un progetto:

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

Esegui tutti i test ( regolari e di integrazione):

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

Esegui solo test regolari :

go test -v ./...

Questa soluzione funziona bene senza strumenti, ma un Makefile o alcuni alias possono renderlo più facile per l'utente. Può anche essere facilmente integrato in qualsiasi IDE che supporti l'esecuzione di go test.

L'esempio completo può essere trovato qui: https://github.com/sagikazarmark/modern-go-application

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.