2009-11-20 1 views
5

Sto immergendo le dita dei piedi in come testare materiale multi-thread, ma non sono sicuro di come iniziare. Sono sicuro che capirà di più roba più facile se potessi ottenere solo roba in corso, quindi mi chiedevo se qualcuno potesse aiutarmi a scrivere un banco di prova NUnit per questa semplice classe:C#: Come testare una classe di lavoro threaded di base

class Worker 
{ 
    public event EventHandler<EventArgs> Done = (s, e) => { }; 

    public void StartWork() 
    { 
     var thread = new Thread(Work) { Name = "Worker Thread" }; 
     thread.Start(); 
    } 

    private void Work() 
    { 
     // Do some heavy lifting 
     Thread.Sleep(500); 
     Done(this, EventArgs.Empty); 
    } 
} 

Quello che vorrei il test è semplicemente: l'evento Done si alza quando finisce. Non avrei problemi se fosse sincrono, ma non sono sicuro da dove cominciare quando non lo è. Un semplice test non se è stato multi-threaded (e il metodo Work non era privato) potrebbe essere:

[TestFixture] 
class WorkerTests 
{ 
    [Test] 
    public void DoWork_WhenDone_EventIsRaised() 
    { 
     var worker = new Worker(); 

     var eventWasRaised = false; 
     worker.Done += (s, e) => eventWasRaised = true; 

     worker.Work(); 
     Assert.That(eventWasRaised); 
    } 
} 

Tutti gli indicatori?

risposta

7

È necessario utilizzare un ManualResetEvent - vedere Unit Testing Multi-Threaded Asynchronous Events per ulteriori dettagli.

Qualcosa di simile:

[Test] 
public void DoWork_WhenDone_EventIsRaised() 
{ 
    var worker = new Worker(); 

    var eventWasRaised = false; 
    var mre = new ManualResetEvent(false); 
    worker.Done += (s, e) => { eventWasRaised= true; mre.Set(); }; 

    worker.Work(); 
    mre.WaitOne(1000); 
    Assert.That(eventWasRaised); 
} 
+2

eventRaisedFlag può essere omesso. È più semplice usare l'evento come bandiera. Assert.That (mre.WaitOne (1000)); –

+0

Oooh, ora è intelligente ... geniale! @Vadmyst: come funzionerebbe se si dice che l'evento è riuscito a finire prima di chiamare WaitOne? – Svish

+0

Oh, suppongo che questo è il motivo per cui dovresti usare un ManualResetEvent invece di un AutoResetEvent? – Svish

1

Il problema principale che si trova con le applicazioni di test filettata è in realtà stimolante il filo con i dati di test, perché è necessario bloccare il thread principale di aspettare fino a quando le altre uscite di thread.

Il modo in cui abbiamo lavorato è testarlo in modo sincrono come suggerito. Questo ti permette di testare il comportamento logico, ma ovviamente non rileverà deadlock e condizioni di gara (non che i test possano far valere facilmente queste cose comunque).

1

È possibile utilizzare uno schema comune che espone la creazione del thread alla classe esterna.

Nella classe estrarre la creazione thread metodo virtuale:

class Worker 
{ 
    public event EventHandler<EventArgs> Done = (s, e) => { }; 

    public void StartWork() 
    { 
     var thread = CreateThread(); 
     thread.Start(); 
    } 

    // Seam for extension and testability 
    virtual protected Thread CreateThread() 
    { 
     return new Thread(Work) { Name = "Worker Thread" }; 
    } 

    private void Work() 
    { 
     // Do some heavy lifting 
     Thread.Sleep(500); 
     Done(this, EventArgs.Empty); 
    } 
} 

Definire sottoclasse che espone il filo:

class WorkerForTest : Worker 
{ 
    internal Thread thread; 

    protected override Thread CreateThread() 
    { 
     thread = base.CreateThread(); 
     return thread; 
    } 
} 

sincronizzare il test con il filo:

[TestFixture] 
class WorkerTests 
{ 
    [Test] 
    public void DoWork_WhenDone_EventIsRaised() 
    { 
     var worker = new WorkerForTest(); 

     var eventWasRaised = false; 
     worker.Done += (s, e) => eventWasRaised = true; 

     worker.StartWork(); 

     // Use the seam for synchronizing the thread in the test 
     worker.thread.Join(); 
     Assert.That(eventWasRaised); 
    } 
} 

Questo caso di progettazione per testabilità presenta vantaggi rispetto alla sincronizzazione del filo di prova con puttin g per dormire prima Assert:

  • Non avrà fallimenti falsi negativi come potrebbe essere quando si mette il thread di test in stop per un periodo che di solito è sufficiente per il thread di lavoro.
  • Non verrà eseguito più lentamente di quanto necessario perché il tempo di sospensione richiede il buffer per essere sicuri. Questo è importante quando molti test nella suite dipendono da questo.
1

Non ci possono essere due opzioni qui: 1) aggiungere un metodo di attesa per il lavoratore in modo da poter attendere il completamento 2) Al posto del semplice oggetto evento uso booleana (AutoResetEvent)

In generale, ogni attendere deve aspettare il timeout specificato. Nei campioni qui sotto l'attesa è infinita.

Prima Opzione:

class Worker 
{ 
//... 
    Thread thread; 

    public void StartWork() 
    { 
     thread = new Thread(Work) { Name = "Worker Thread" }; 
     thread.Start(); 
    } 

    void WaitCompletion() 
    { 
    if (thread != null) thread.Join(); 
    } 
//... 
} 

[TestFixture] 
class WorkerTests 
{ 
    [Test] 
    public void DoWork_WhenDone_EventIsRaised() 
    { 
     var worker = new Worker(); 

     var eventWasRaised = false; 
     worker.Done += (s, e) => eventWasRaised = true; 

     worker.Work(); 
     worker.WaitCompletion(); 

     Assert.That(eventWasRaised); 
    } 
} 

Seconda opzione: (attesa può essere fatto con timeout)

[TestFixture] 
class WorkerTests 
{ 
    [Test] 
    public void DoWork_WhenDone_EventIsRaised() 
    { 
     var worker = new Worker(); 

     AutoResetEvent eventWasRaised = new AutoResetEvent(false); 
     worker.Done += (s, e) => eventWasRaised.Set(); 

     worker.Work(); 
     Assert.That(eventWasRaised.WaitOne()); 
    } 
}