2009-05-18 19 views
10

piaccia o no, a volte devi scrivere test per le classi che fanno uso interno dei timer.Come si fanno a testare le classi che utilizzano i timer internamente?

diciamo per esempio una classe che prende segnalazioni di disponibilità del sistema e genera un evento se il sistema è stato giù per troppo tempo

public class SystemAvailabilityMonitor { 
    public event Action SystemBecameUnavailable = delegate { }; 
    public event Action SystemBecameAvailable = delegate { }; 
    public void SystemUnavailable() { 
     //.. 
    } 
    public void SystemAvailable() { 
     //.. 
    } 
    public SystemAvailabilityMonitor(TimeSpan bufferBeforeRaisingEvent) { 
     //.. 
    } 
} 

Ho un paio di trucchi che uso (pubblicheremo questi come una risposta) ma mi chiedo che cosa facciano le altre persone dal momento che non sono pienamente soddisfatto di nessuno dei miei approcci.

risposta

8

Estraggo il timer dall'oggetto che reagisce all'allarme. In Java è possibile passarlo ad esempio ScheduledExecutorService. Nei test unitari, passo all'attuazione che posso controllare in modo deterministico, come ad esempio jMock's DeterministicScheduler.

+0

Sì, che una sorta di doppia spedizione (credo?) sarebbe l'approccio ideale, peccato che .NET non riesca a farlo. A volte faccio scorrere la mia interfaccia del timer, ma mi sento sempre come se stessi introducendo la complessità. –

+0

Suona più come un'iniezione di dipendenza che una doppia spedizione. –

+0

Minore problema di terminologia, ma penso che sia corretto chiamare questo doppio invio o visitatore.È sicuro, ma stai anche prendendo un oggetto e dicendo di applicare la sua politica di aggiornamento a "questo". –

0

I modi che di solito gestire questa sono o

  1. Impostare il timer a spuntare sempre 100 millisecondi e capire che la sua probabile che il mio thread sarà stata commutata a da allora. Questo è imbarazzante e produce risultati un po 'indeterministici.
  2. Collegare l'evento trascorso del timer a un evento Tick() interno o protetto. Quindi dal test impostare l'intervallo del timer su qualcosa di molto grande e attivare manualmente il metodo Tick() dal test. Questo ti dà test deterministici ma ci sono alcune cose che non puoi semplicemente testare con questo approccio.
0

Rifacifico questi in modo che il valore temporale sia un parametro del metodo e quindi crei un altro metodo che non faccia altro che passare il parametro corretto. In questo modo tutto il comportamento reale è isolato e facilmente verificabile su tutti i casi di bordo, lasciando solo l'inserimento del parametro molto banale non testato.

Come esempio estremamente banale, se ho iniziato con questo:

public long timeElapsedSinceJan012000() 
{ 
    Date now = new Date(); 
    Date jan2000 = new Date(2000, 1, 1); // I know...deprecated...bear with me 
    long difference = now - jan2000; 
    return difference; 
} 

avrei refactoring di questo, e unità testare il secondo metodo:

public long timeElapsedSinceJan012000() 
{ 
    return calcDifference(new Date()); 
} 

public long calcDifference(Date d) { 
    Date jan2000 = new Date(2000, 1, 1); 
    long difference = d - jan2000; 
    return difference; 
} 
+1

Non sono sicuro di averlo capito, in che modo coinvolgere i timer? –

1

Suona come si dovrebbe giro il timer ma ahimè ... dopo un rapido Google this other SO question con alcune risposte è stato il colpo di ricerca in alto. Ma poi ho capito che la domanda riguardava le classi che usavano i timer internamente, doh. Comunque, quando si fa la programmazione di giochi/motori, a volte si passano i timer come parametri di riferimento ai costruttori, il che li renderebbe di nuovo irridenti possibili, immagino? Ma poi di nuovo, sono il programmatore noob ^^

+0

No hai ragione, il modo ideale per farlo è passare in un oggetto timer, l'unico problema è che questo rompe alcune convenzioni di .NET Framework e si sente terribilmente pesante per alcune cose (ora devo configurare il mio contenitore IoC con ancora un altro oggetto, per esempio) –

3

Questo è quello che sto usando. L'ho trovato nel libro: Test Driven - TDD pratico e TDD di accettazione per gli sviluppatori Java di Lasse Koskela.

public interface TimeSource { 
    long millis(); 
} 


public class SystemTime { 

    private static TimeSource source = null; 

    private static final TimeSource DEFAULTSRC = 
     new TimeSource() { 
     public long millis() { 
      return System.currentTimeMillis(); 
     } 
    }; 


    private static TimeSource getTimeSource() { 
     TimeSource answer; 
     if (source == null) { 
      answer = DEFAULTSRC; 
     } else { 
      answer = source; 
     } 
     return answer; 
    } 

    public static void setTimeSource(final TimeSource timeSource) { 
     SystemTime.source = timeSource; 
    } 

    public static void reset() { 
     setTimeSource(null); 
    } 

    public static long asMillis() { 
     return getTimeSource().millis(); 
    } 

    public static Date asDate() { 
     return new Date(asMillis()); 
    } 

} 

Si noti che la fonte di tempo predefinito, DEFAULTSRC, è System.currentTimeMillis(). È sostituito nei test unitari; tuttavia, il normale comportamento è il tempo di sistema standard.

Questo è dove viene utilizzato:

public class SimHengstler { 

    private long lastTime = 0; 

    public SimHengstler() { 
     lastTime = SystemTime.asMillis(); //System.currentTimeMillis(); 
    } 
} 

E qui è la prova di unità:

import com.company.timing.SystemTime; 
import com.company.timing.TimeSource; 

public class SimHengstlerTest { 
    @After 
    public void tearDown() { 
     SystemTime.reset(); 
    } 

    @Test 
    public final void testComputeAccel() { 
     // Setup 
     setStartTime(); 
     SimHengstler instance = new SimHengstler(); 
     setEndTime(1020L); 
    } 
    private void setStartTime() { 
     final long fakeStartTime = 1000L; 
     SystemTime.setTimeSource(new TimeSource() { 
      public long millis() { 
       return fakeStartTime; 
      } 
     }); 
    } 
    private void setEndTime(final long t) { 
     final long fakeEndTime = t; // 20 millisecond time difference 
     SystemTime.setTimeSource(new TimeSource() { 
      public long millis() { 
       return fakeEndTime; 
      } 
     }); 
    } 

Nel test di unità, ho sostituito il TimeSource con solo un numero che è stato fissato a 1000 millisecondi. Questo servirà come orario di partenza. Quando si chiama setEndTime(), inserisco 1020 millisecondi per il tempo di completamento. Questo mi ha dato una differenza di tempo controllata di 20 millisecondi.

Non c'è alcun codice di test nel codice di produzione, solo per ottenere il normale orario di sistema.

Assicurarsi di chiamare reset dopo il test per tornare a utilizzare il metodo orario del sistema anziché il tempo falso.

0

Mi rendo conto che questa è una domanda Java, ma potrebbe essere interessante mostrare come è stata fatta nel mondo Perl. Puoi semplicemente ignorare le funzioni temporali principali nei tuoi test. :) Questo può sembrare orribile, ma significa che non devi iniettare un sacco di riferimenti indiretti nel tuo codice di produzione solo per testarlo. Test::MockTime è un esempio. Il tempo di congelamento nel test rende alcune cose molto più semplici. Come quei toccanti test comparativi di tempo non atomici in cui esegui qualcosa al tempo X e quando controlli X + 1. C'è un esempio nel codice qui sotto.

Un po 'più convenzionale, di recente ho avuto una classe PHP per estrarre dati da un database esterno. Volevo che succedesse al massimo una volta ogni X secondi. Per testarlo inserisco sia l'ultimo tempo di aggiornamento che l'intervallo di tempo di aggiornamento come attributi dell'oggetto. Inizialmente li avevo fatti costanti, quindi questo cambiamento per i test ha anche migliorato il codice. Poi il test potrebbe giocherellare con quei valori in questo modo:

function testUpdateDelay() { 
    $thing = new Thing; 

    $this->assertTrue($thing->update, "update() runs the first time"); 

    $this->assertFalse($thing->update, "update() won't run immediately after"); 

    // Simulate being just before the update delay runs out 
    $just_before = time() - $thing->update_delay + 2; 
    $thing->update_ran_at = $just_before; 
    $this->assertFalse($thing->update, "update() won't run just before the update delay runs out"); 
    $this->assertEqual($thing->update_ran_at, $just_before, "update_ran_at unchanged"); 

    // Simulate being just after 
    $just_after = time() - $thing->update_delay - 2; 
    $thing->update_ran_at = $just_after; 
    $this->assertTrue($thing->update, "update() will run just after the update delay runs out"); 

    // assertAboutEqual() checks two numbers are within N of each other. 
    // where N here is 1. This is to avoid a clock tick between the update() and the 
    // check 
    $this->assertAboutEqual($thing->update_ran_at, time(), 1, "update_ran_at updated"); 
} 
4

Se siete alla ricerca di risposte a questo problema, si potrebbe essere interessato a questo blog: http://thorstenlorenz.blogspot.com/2009/07/mocking-timer.html

In essa ho spiegare un modo per ignorare il solito comportamento di una classe System.Timers.Timer, per attivarlo su Start().

Ecco la versione breve:

class FireOnStartTimer : System.Timers.Timer 
{ 
public new event System.Timers.ElapsedEventHandler Elapsed; 

public new void Start() 
{ 
    this.Elapsed.Invoke(this, new EventArgs() as System.Timers.ElapsedEventArgs); 
} 
} 

Naturalmente questo richiede di essere in grado di passare il timer nella classe in prova. Se ciò non è possibile, allora il design della classe è difettoso quando si tratta di testabilità poiché non supporta l'iniezione di dipendenza. Dovresti cambiare il suo design se puoi. Altrimenti potresti essere sfortunato e non essere in grado di testare nulla su quella classe che coinvolge il suo timer interno.

Per una spiegazione più approfondita visitare il blog.

Problemi correlati