2015-05-24 7 views
7

Nel mio codice sono presenti alcuni componenti che dispongono di go-routin persistenti che ascoltano gli eventi per attivare azioni. La maggior parte delle volte, non vi è alcun motivo (al di fuori dei test) per consentire loro di inviare una notifica quando hanno completato tale azione.Test per risultati asincroni senza sleep in Go

Tuttavia, i miei Unittests stanno usando il sonno di aspettare per questi compiti asincroni per completare:

// Send notification event. 
mock.devices <- []sparkapi.Device{deviceA, deviceFuncs, deviceRefresh} 

// Wait for go-routine to process event. 
time.Sleep(time.Microsecond) 

// Check that no refresh method was called. 
c.Check(mock.actionArgs, check.DeepEquals, mockFunctionCall{}) 

che sembra rotto, ma non sono stati in grado di trovare una soluzione migliore che non aggiunge sovraccarico irragionevole per l'utilizzo non test. C'è una soluzione ragionevole che ho perso?

risposta

4

Soheil Hassas La soluzione di Yeganeh è di solito una buona strada da percorrere, o almeno qualcosa di simile. Ma è una modifica all'API, e può creare un sovraccarico per il chiamante (anche se non molto, il chiamante non ha ha per passare un canale Done se il chiamante non ne ha bisogno). Detto questo, ci sono casi in cui non si desidera quel tipo di sistema ACK.

Consiglio vivamente il pacchetto di prova Gomega per questo tipo di problema. È progettato per funzionare con Ginkgo, ma può essere utilizzato autonomamente. Include un eccellente supporto asincrono tramite gli abbinamenti Consistently e Eventually.

Detto questo, mentre Gomega funziona bene con i sistemi di test non BDD (e si integra perfettamente in testing), è una cosa piuttosto grande e può essere un impegno. Se vuoi solo quell'unico pezzo, puoi scrivere la tua versione di queste asserzioni. Ti consiglio comunque di seguire l'approccio di Gomega, che esegue il polling piuttosto che un singolo sleep (questo dorme ancora, non è possibile risolverlo senza ridisegnare l'API).

Ecco come osservare le cose in prova. Si crea una funzione di supporto del tipo:

http://play.golang.org/p/qpdEOsWYh0

const iterations = 10 
const interval = time.Millisecond 

func Consistently(f func()) { 
    for i := 0; i < iterations; i++ { 
     f() // Assuming here that `f()` panics on failure 
     time.Sleep(interval) 
    } 
} 

mock.devices <- []sparkapi.Device{deviceA, deviceFuncs, deviceRefresh} 
Consistently(c.Check(mock.actionArgs, check.DeepEquals, mockFunctionCall{})) 

Ovviamente è possibile modificare le iterazioni e l'intervallo per soddisfare le vostre esigenze. (Gomega usa un timeout di 1 secondo, polling ogni 10ms.)

Lo svantaggio di qualsiasi implementazione di Consistently è che qualunque sia il tuo timeout, devi mangiare ogni test eseguito. Ma non c'è davvero alcun modo per aggirare questo. Devi decidere quanto è lungo abbastanza per "non accadere". Quando possibile, è bello girare il test in giro per verificare Eventually, dal momento che può avere successo più velocemente.

Eventually è un po 'più complicato, dal momento che è necessario utilizzare recover per prendere il panico fino a quando non riesce, ma non è male.Qualcosa di simile a questo:

func Eventually(f func()) { 
    for i := 0; i < iterations; i++ { 
     if !panics(f) { 
      return 
     } 
     time.Sleep(interval) 
    } 
    panic("FAILED") 
} 

func panics(f func()) (success bool) { 
    defer func() { 
     if e := recover(); e != nil { 
      success = true 
     } 
    }() 
    f() 
    return 
} 

In definitiva, questa è solo una versione leggermente più complicata di quello che hai, ma avvolge la logica su in una funzione in modo che legge un po 'meglio.

6

Il modo idiomatico consiste nel passare un canale done insieme ai propri dati alla routine di routine del lavoratore. Il go-routine dovrebbe close il canale done e il codice dovrebbe attendere che il canale è chiuso:

done := make(chan bool) 

// Send notification event. 
mock.devices <- Job { 
    Data: []sparkapi.Device{deviceA, deviceFuncs, deviceRefresh}, 
    Done: done, 
} 

// Wait until `done` is closed. 
<-done 

// Check that no refresh method was called. 
c.Check(mock.actionArgs, check.DeepEquals, mockFunctionCall{}) 

Usando questo modello, è anche possibile implementare un timeout per il test:

// Wait until `done` is closed. 
select { 
case <-done: 
case <-time.After(10 * time.Second): 
    panic("timeout") 
} 
+0

Stavo cercando di mantenere la domanda semplice, ma forse ho finito di farlo .. Sto inviando dati a un componente che può passare ad altri componenti, che possono passare ad altri componenti. La catena di passaggio può includere rami e forche e tutti i tipi di complessità. Questo rende questo (altrimenti eccellente) suggerimento non così utile. – DonGar

+0

Sì, questo è il punto. Tutti i componenti della catena dovrebbero ricevere il canale 'done' come parte del loro lavoro aysnc. Altrimenti, sprecherai CPU e le tue go-routine dovranno svegliarsi e sondare. –

+0

@DonGar Potresti anche essere interessato a 'net/context' e http://blog.golang.org/pipelines. Esistono alcuni modelli Go esistenti molto validi per catene complesse di componenti incrociati. (Non cambia la domanda di testabilità, ma può influenzare la progettazione della concorrenza.) –