2010-03-02 17 views

risposta

9

È possibile testare l'unità non creando effettivamente una dipendenza diretta su System.Threading.Timer. Creare invece un'interfaccia ITimer e un wrapper attorno a System.Threading.Timer che lo implementa.

In primo luogo è necessario convertire la richiamata a un evento, in modo che possa essere parte di un'interfaccia:

public delegate void TimerEventHandler(object sender, TimerEventArgs e); 

public class TimerEventArgs : EventArgs 
{ 
    public TimerEventArgs(object state) 
    { 
     this.State = state; 
    } 

    public object State { get; private set; } 
} 

Poi creare un'interfaccia:

public interface ITimer 
{ 
    void Change(TimeSpan dueTime, TimeSpan period); 
    event TimerEventHandler Tick; 
} 

E un wrapper:

public class ThreadingTimer : ITimer, IDisposable 
{ 
    private Timer timer; 

    public ThreadingTimer(object state, TimeSpan dueTime, TimeSpan period) 
    { 
     timer = new Timer(TimerCallback, state, dueTime, period); 
    } 

    public void Change(TimeSpan dueTime, TimeSpan period) 
    { 
     timer.Change(dueTime, period); 
    } 

    public void Dispose() 
    { 
     timer.Dispose(); 
    } 

    private void TimerCallback(object state) 
    { 
     EventHandler tick = Tick; 
     if (tick != null) 
      tick(this, new TimerEventArgs(state)); 
    } 

    public event TimerEventHandler Tick; 
} 

Ovviamente si aggiungerebbe qualsiasi sovraccarico del costruttore e/o Change metodo che è necessario utilizzare da Threading.Timer. Ora è possibile test di unità qualsiasi cosa a seconda ITimer con un timer falso:

public class FakeTimer : ITimer 
{ 
    private object state; 

    public FakeTimer(object state) 
    { 
     this.state = state; 
    } 

    public void Change(TimeSpan dueTime, TimeSpan period) 
    { 
     // Do nothing 
    } 

    public void RaiseTickEvent() 
    { 
     EventHandler tick = Tick; 
     if (tick != null) 
      tick(this, new TimerEventArgs(state)); 
    } 

    public event TimerEventHandler Tick; 
} 

Ogni volta che si desidera simulare un segno di spunta, basta chiamare RaiseTickEvent sul falso.

[TestMethod] 
public void Component_should_respond_to_tick 
{ 
    ITimer timer = new FakeTimer(someState); 
    MyClass c = new MyClass(timer); 
    timer.RaiseTickEvent(); 
    Assert.AreEqual(true, c.TickOccurred); 
} 
+0

Questo sembra buono, ma perché si crea il proprio delegato invece di utilizzare semplicemente il delegato TimerCallback dal BCL? – Shaun

+1

@Shaun: poiché quel delegato non è un gestore eventi valido. I gestori di eventi hanno una firma specifica: 'object mittente, TEventArgs e' dove' TEventArgs: EventArgs'. – Aaronaught

+0

Ti dispiace, in questi giorni con le chiusure supportate così facilmente, se volevo qualcosa di veloce e sporco, non so se mi sarei preoccupato di tutte le dichiarazioni degli eventi e della connessione, potrei semplicemente passare in un 'Azione 'invece. – Aaronaught

1

mi metterà alla prova nel allo stesso modo di qualsiasi altra classe ma con intervalli di tempo brevi per evitare che il test unitario funzioni a lungo. Un altro approccio è quello di testare solo il proprio codice e l'utilizzo di un timer simulato (ad esempio NMock), ma dipende da come è il design del codice. Puoi pubblicare alcuni frammenti di codice?

0

Se l'ora cambia semplicemente il metodo di callback, è sufficiente verificare la correttezza di tale metodo e non il modo in cui funziona l'ora del sistema.

Oltre a questo, mi consiglia di utilizzare il MultimediaTimer invece - è molto più preciso.

+0

Dire che voglio un timer che spara una volta all'ora. Raccomandi 'MultimediaTimer' per tutti? :) –

+0

Sì, e combinalo con .NET Dispatcher se è necessario restituire il thread dell'interfaccia utente. –

0

Si spera che qualunque sia questa funzione è una parte più ampia di consente di configurare l'intervallo del timer. Dovresti essere in grado di usare quell'interfaccia per iniettare un breve intervallo adatto per il test dell'unità. Ho la domanda sul valore di questo test: stai testando l'intervallo (inutile) o che la routine viene effettivamente chiamata all'incirca ogni tanto?