2009-11-20 13 views
5

Estoy sumergiendo los pies en la forma de probar cosas de subprocesos múltiples, pero no estoy muy seguro de cómo empezar. Estoy seguro de que voy a averiguar más cosas fuera más fácil si tan sólo pudiera conseguir cosas en marcha, así que me preguntaba si alguien podría ayudarme a escribir un caso de prueba NUnit para esta clase simple:C#: Cómo probar una clase de trabajador con subprocesos básicos

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); 
    } 
} 

Lo que me gustaría la prueba es simple: se produce el evento Done cuando termina. No tendría problemas si fuera sincrónico, pero no estoy seguro de por dónde empezar cuando no lo es. No si fue multi-hilo Una prueba simple (y el método Work no era privado) podría ser:

[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); 
    } 
} 

Cualquier punteros?

Respuesta

7

Tiene que utilizar un evento ManualResetEvent - vea Unit Testing Multi-Threaded Asynchronous Events para más detalles.

Algo así como:

[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 se puede omitir. Es más simple usar evento como una bandera. Assert.That (mre.WaitOne (1000)); –

+0

Oooh, ahora eso es inteligente ... ¡genial! @Vadmyst: ¿cómo funcionaría si dijera que el evento logró finalizar antes de llamar a WaitOne? – Svish

+0

Oh, supongo que es por eso que usaría un evento manualResetEvent en lugar de un evento AutoResetEvent? – Svish

1

El principal problema que encuentre con las aplicaciones de prueba roscado es en realidad estimula el hilo con los datos de prueba, ya que necesitará para bloquear el hilo principal que esperar hasta que el otro hilo termina.

La forma en que hemos trabajado con esto es probarlo sincrónicamente como usted sugiera. Esto le permite probar el comportamiento lógico, pero no detectará bloqueos y condiciones de carrera, por supuesto (no es que las pruebas puedan afirmar estas cosas fácilmente de todos modos).

1

Puede usar un patrón común que expone la creación del hilo a la clase externa.

En la clase de extraer la creación del hilo con el método virtual:

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); 
    } 
} 

Definir sub-clase que expone el hilo:

class WorkerForTest : Worker 
{ 
    internal Thread thread; 

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

sincronizar la prueba con el hilo:

[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); 
    } 
} 

Este caso de diseño para la capacidad de prueba tiene ventajas sobre la sincronización del hilo de prueba al poner g a dormir antes de aserción:

  • No tendrá fallos falsos negativos, como podría ser la hora de poner el hilo de prueba para dormir para el período que por lo general es suficiente para subproceso de trabajo para terminar.
  • No se ejecutará más lento de lo debido, ya que el tiempo de inactividad toma el búfer para asegurarse. Esto es importante cuando muchas pruebas en la suite dependen de ello.
1

No puede haber dos opciones: 1) Añadir un método Wait al trabajador para que pueda esperar a la finalización 2) en lugar de sencilla utilización booleano objeto de evento (AutoResetEvent)

En general, todas las espera tiene que esperar el tiempo de espera especificado. En las muestras a continuación la espera es infinita.

Primera Opción:

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); 
    } 
} 

Segunda opción: (Espera se puede hacer con tiempo de espera)

[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()); 
    } 
} 
Cuestiones relacionadas