2015-06-06 19 views
10

Dire, ho avuto il seguente codice che stampa alcuni messaggi di registro. Come posso verificare che i messaggi corretti siano stati registrati? Come log.Fatal chiamate os.Exit(1) i test falliscono.Come testare la funzione Go contenente log.Fatal()

package main 

import (
    "log" 
) 

func hello() { 
    log.Print("Hello!") 
} 

func goodbye() { 
    log.Fatal("Goodbye!") 
} 

func init() { 
    log.SetFlags(0) 
} 

func main() { 
    hello() 
    goodbye() 
} 

Ecco le prove di ipotetici:

package main 

import (
    "bytes" 
    "log" 
    "testing" 
) 


func TestHello(t *testing.T) { 
    var buf bytes.Buffer 
    log.SetOutput(&buf) 

    hello() 

    wantMsg := "Hello!\n" 
    msg := buf.String() 
    if msg != wantMsg { 
     t.Errorf("%#v, wanted %#v", msg, wantMsg) 
    } 
} 

func TestGoodby(t *testing.T) { 
    var buf bytes.Buffer 
    log.SetOutput(&buf) 

    goodbye() 

    wantMsg := "Goodbye!\n" 
    msg := buf.String() 
    if msg != wantMsg { 
     t.Errorf("%#v, wanted %#v", msg, wantMsg) 
    } 
} 

risposta

4

Questo è simile a "How to test os.Exit() scenarios in Go": è necessario implementare il proprio logger, che di default reindirizzare a log.xxx(), ma ti dà la possibilità, quando i test, in sostituzione di un funzione come log.Fatalf() con il proprio (che non richiede os.Exit(1))

ho fatto lo stesso per il test os.Exit() chiamate in exit/exit.go:

exiter = New(func(int) {}) 
exiter.Exit(3) 
So(exiter.Status(), ShouldEqual, 3) 

(qui, la mia funzione "uscita" è una vuota che non fa niente)

-2

Non si può e non si deve. Questo "devi" testare "ogni singola riga" - l'aspetto è strano, specialmente per le condizioni del terminale e questo è ciò che log.Fatal è per. (O semplicemente testare dall'esterno.)

+5

"Can not" è un po 'forte; ma sono d'accordo con "non dovrebbe". Se vuoi testare una condizione di fallimento di un blocco (non principale) di codice, allora non usare 'log.Fatal' per quel codice ma restituisci un' error' invece (es.se l'errore è un'opzione, avere un'API che lo rappresenta). Nella stragrande maggioranza dei casi 'log.Fatal' dovrebbe essere usato solo nelle funzioni' main', o 'init' (o possibilmente alcune cose che devono essere chiamate solo direttamente da esse). –

+1

in un sistema distribuito complicato ci sono scenari in cui il mio codice non può eseguire il suo compito, ad esempio un database è andato giù o manca la configurazione. preferisco Fatal qui, in primo luogo per evitare l'esecuzione con lo stato del programma indeterminato e il secondo per garantire che ottengo un avviso di monitoraggio – Plato

2

Ho utilizzato il seguente codice per testare la mia funzione. in xxx.go:

var logFatalf = log.Fatalf 

if err != nil { 
    logFatalf("failed to init launcher, err:%v", err) 
} 

E in xxx_test.go:

// TestFatal is used to do tests which are supposed to be fatal 
func TestFatal(t *testing.T) { 
    origLogFatalf := logFatalf 

    // after this test, replace the original fatal function 
    defer func() { logFataf = origLogFatalf }() 

    errors := []string{} 
    logFatalf = func(format string, args ...interface{}) { 
     if len(args) > 0 { 
      errors = append(errors, fmt.Sprintf(format, args)) 
     } else { 
      errors = append(errors, format) 
     } 
    } 
    if len(errors) != 1 { 
     t.Errorf("excepted one error, actual %v", len(errors)) 
    } 
} 
0

userei il bouk/monkey pacchetto estremamente maneggevole (qui insieme a stretchr/testify).

func TestGoodby(t *testing.T) { 
    wantMsg := "Goodbye!" 

    fakeLogFatal := func(msg ...interface{}) { 
    assert.Equal(t, wantMsg, msg[0]) 
    panic("log.Fatal called") 
    } 
    patch := monkey.Patch(log.Fatal, fakeLogFatal) 
    defer patch.Unpatch() 
    assert.PanicsWithValue(t, "log.Fatal called", goodbye, "log.Fatal was not called") 
} 

vi consiglio la lettura del caveats to using bouk/monkey prima di andare questo itinerario.

2

Mentre è possibile testare il codice che contiene log.Fatal, non è consigliato. In particolare, non è possibile testare quel codice in un modo supportato dal flag -cover su go test.

Invece si consiglia di cambiare il codice per restituire un errore invece di chiamare log.Fatal. In una funzione sequenziale è possibile aggiungere un valore di ritorno aggiuntivo e in una goroutine è possibile passare un errore su un canale di tipo chan error (o qualche tipo di struttura contenente un campo di errore di tipo).

Una volta apportate le modifiche, il codice sarà molto più facile da leggere, molto più facile da testare e sarà più portabile (ora è possibile utilizzarlo in un programma server oltre agli strumenti da riga di comando).

Se si dispone di chiamate log.Println, si consiglia inoltre di passare un registratore personalizzato come campo su un ricevitore. In questo modo è possibile accedere al logger personalizzato, che è possibile impostare su stderr o stdout per un server, e un logger noop per i test (in modo da non ottenere una serie di output non necessari nei test). Il pacchetto log supporta i logger personalizzati, quindi non è necessario scrivere il proprio o importare un pacchetto di terze parti per questo.

Problemi correlati