In questo tutorial ti insegnerò tutte le basi dei test idiomatici in Go, utilizzando le migliori pratiche sviluppate dai progettisti di linguaggi e dalla comunità. L'arma principale sarà il pacchetto di test standard. L'obiettivo sarà un programma di esempio che risolve un problema semplice da Project Euler.
Go è un linguaggio di programmazione incredibilmente potente, impara tutto da scrivere semplici utility a costruire server web scalabili e flessibili nel nostro corso completo.
Il problema della somma quadrata è piuttosto semplice: "Trova la differenza tra la somma dei quadrati dei primi cento numeri naturali e il quadrato della somma."
Questo particolare problema può essere risolto in modo piuttosto conciso, specialmente se conosci il tuo Gauss. Ad esempio, la somma dei primi N numeri naturali è (1 + N) * N / 2
, e la somma dei quadrati dei primi N interi è: (1 + N) * (N * 2 + 1) * N / 6
. Quindi l'intero problema può essere risolto con la seguente formula e assegnando da 100 a N:
(1 + N) * (N * 2 + 1) * N / 6 - ((1 + N) * N / 2) * ((1 + N) * N / 2)
Bene, questo è molto specifico e non c'è molto da testare. Invece, ho creato alcune funzioni che sono un po 'più generali di ciò che è necessario per questo problema, ma possono servire per altri programmi in futuro (il progetto Euler ha 559 problemi al momento).
Il codice è disponibile su GitHub.
Ecco le firme delle quattro funzioni:
// La funzione MakeIntList () restituisce una matrice di interi consecutivi // partendo da 1 fino al 'numero' (incluso il numero) func MakeIntList (numero int) [] int // La funzione squareList () prende una sezione di interi e restituisce un // array dei quares di questi interi func SquareList (numbers [] int) [] int // La funzione sumList () accetta una porzione di interi e restituisce la loro funzione sum SumList (numeri [] int) int // Risolvi Progetto Eulero # 6 - Somma differenza quadrata func Processo (numero int) int
Ora, con il nostro programma target in atto (ti prego perdonami, fanatici del TDD), vediamo come scrivere test per questo programma.
Il pacchetto di test va di pari passo con il vai alla prova
comando. I test del pacchetto devono essere inseriti nei file con il suffisso "_test.go". È possibile dividere i test su diversi file che seguono questa convenzione. Ad esempio: "whatever1_test.go" e "whatever2_test.go". Dovresti inserire le tue funzioni di test in questi file di test.
Ogni funzione di test è una funzione esportata pubblicamente il cui nome inizia con "Test", accetta un puntatore a a testing.T
oggetto, e non restituisce nulla. Sembra:
func TestWhatever (t * testing.T) // Il tuo codice di prova va qui
L'oggetto T fornisce vari metodi che è possibile utilizzare per indicare errori o errori di registrazione.
Ricorda: solo le funzioni di test definite nei file di test verranno eseguite da vai alla prova
comando.
Ogni test segue lo stesso flusso: impostare l'ambiente di test (opzionale), inserire il codice sotto input di test, acquisire il risultato e confrontarlo con l'output previsto. Si noti che input e risultati non devono essere argomenti per una funzione.
Se il codice in prova sta recuperando i dati da un database, l'input si accerterà che il database contenga dati di test appropriati (che potrebbero comportare il mocking a vari livelli). Ma, per la nostra applicazione, è sufficiente lo scenario comune di passare gli argomenti di input a una funzione e confrontare il risultato con l'output della funzione.
Iniziamo con ListaSomma ()
funzione. Questa funzione prende una porzione di numeri interi e restituisce la loro somma. Ecco una funzione di test che verifica ListaSomma ()
si comporta come dovrebbe.
Mette alla prova due casi di test, e se un risultato atteso non corrisponde al risultato, chiama il Errore()
metodo dell'oggetto test.T..
func TestSumList_NotIdiomatic (t * testing.T) // Test [] -> 0 result: = SumList ([] int ) if result! = 0 tEError ("Per input:", [] int , "expected:", 0, "got:", result) // Test [] 4, 8, 9 -> 21 result = SumList ([] int 4, 8, 9) se il risultato ! = 21 t.Error ("Per input:", [] int , "previsto:", 0, "ottenuto:", risultato)
Questo è tutto semplice, ma sembra un po 'prolisso. Il test Idiomatic Go utilizza test basati su tabelle in cui si definisce una struttura per coppie di input e output previsti e quindi si ha un elenco di queste coppie che si alimentano in un loop con la stessa logica. Ecco come è fatto per testare il ListaSomma ()
funzione.
digita List2IntTestPair struct input [] int output int func TestSumList (t * testing.T) var tests = [] List2IntTestPair [] int , 0, [] int 1, 1, [] int 1, 2, 3, [] int 12, 13, 25, 7, 57, per _, coppia: = range tests result: = SumList (pair.input) se risultato ! = pair.output t.Error ("Per input:", pair.input, "expected:", pair.output, "got:", result)
Questo è molto meglio. È facile aggiungere altri casi di test. È facile avere l'intera gamma di casi di test in un unico posto e, se si decide di modificare la logica di test, non è necessario modificare più istanze.
Ecco un altro esempio per testare il SquareList ()
funzione. In questo caso, sia l'input che l'output sono fette di interi, quindi la struttura della coppia di test è diversa, ma il flusso è identico. Una cosa interessante è che Go non fornisce un modo integrato per confrontare le fette, quindi lo uso reflect.DeepEqual ()
per confrontare la porzione di output con la fetta attesa.
digita List2ListTestPair struct input [] int output [] int func TestSquareList (t * testing.T) var tests = [] List2ListTestPair [] int , [] int , [] int 1 , [] int 1, [] int 2, [] int 4, [] int 3, 5, 7, [] int 9, 25, 49, per _, pair: = range tests result: = SquareList (pair.input) if! reflect.DeepEqual (result, pair.output) t.Error ("Per input:", pair.input, "previsto:" , pair.output, "got:", risultato)
Eseguire test è semplice come digitare vai alla prova
nella tua directory dei pacchetti. Go troverà tutti i file con il suffisso "_test.go" e tutte le funzioni con il prefisso "Test" ed eseguirli come test. Ecco come appare quando tutto va bene:
(G) / project-euler / 6 / go> vai test PASS ok _ / Users / gigi / Documents / dev / github / project-euler / 6 / go 0.006s
Non molto drammatico. Permettetemi di rompere un test di proposito. Cambierò il caso di test per ListaSomma ()
in modo tale che l'uscita prevista per la somma 1 e 2 sarà 7.
func TestSumList (t * testing.T) var tests = [] List2IntTestPair [] int , 0, [] int 1, 1, [] int 1, 2, 7 , [] int 12, 13, 25, 7, 57, per _, pair: = range tests result: = SumList (pair.input) if result! = pair.output t.Error (" Per l'input: ", pair.input," previsto: ", pair.output," got: ", result)
Ora, quando digiti vai alla prova
, ottieni:
(G) / project-euler / 6 / go> go test --- FAIL: TestSumList (0.00s) 006_sum_square_difference_test.go: 80: Per input: [1 2] previsto: 7 got: 3 FAIL exit status 1 FAIL _ / Utenti / gigi / Documenti / dev / github / project-euler / 6 / go 0.006s
Questo spiega abbastanza bene cosa è successo e dovrebbe darti tutte le informazioni di cui hai bisogno per risolvere il problema. In questo caso, il problema è che il test stesso è sbagliato e il valore atteso dovrebbe essere 3. Questa è una lezione importante. Non assumere automaticamente che se un test fallisce, il codice in prova è rotto. Considerare l'intero sistema, che include il codice in prova, il test stesso e l'ambiente di test.
Per garantire che il tuo codice funzioni, non è sufficiente avere test di passaggio. Un altro aspetto importante è la copertura del test. I tuoi test coprono ogni affermazione del codice? A volte anche questo non è abbastanza. Ad esempio, se hai un ciclo nel codice che viene eseguito finché non viene soddisfatta una condizione, puoi testarla correttamente con una condizione che funziona, ma non notare che in alcuni casi la condizione può sempre essere falsa, risultando in un ciclo infinito.
I test unitari sono come lavarsi i denti e usare il filo interdentale. Non dovresti trascurarli. Sono la prima barriera contro i problemi e permetteranno di avere fiducia nel refactoring. Sono anche un vantaggio quando si tenta di riprodurre i problemi e di essere in grado di scrivere un test in errore che dimostra il problema che si verifica dopo aver risolto il problema.
Sono necessari anche test di integrazione. Pensa a loro come a visitare il dentista. Potresti stare bene senza di loro per un po ', ma se li trascuri troppo a lungo non sarà bello.
La maggior parte dei programmi non banali sono costituiti da più moduli o componenti correlati tra loro. I problemi possono spesso verificarsi quando si collegano questi componenti insieme. I test di integrazione ti danno la certezza che tutto il tuo sistema funziona come previsto. Esistono molti altri tipi di test come test di accettazione, test delle prestazioni, prove di carico / carico e test completi del sistema completo, ma test unitari e test di integrazione sono due dei metodi fondamentali per testare il software.
Go ha il supporto integrato per i test, un modo ben definito per scrivere test e linee guida consigliate sotto forma di test basati su tabelle.
La necessità di scrivere strutture speciali per ogni combinazione di input e output è un po 'fastidiosa, ma questo è il prezzo che si paga per l'approccio semplice di Go in base alla progettazione.