2010-08-03 12 views
11

Tengo que probar un método que realiza una cierta cantidad de trabajo después de un intervalo.C# Unit Testing - Thread.Sleep (x) - Cómo simular el reloj del sistema

while (running) 
{ 
    ... 
    // Work 
    ... 
    Thread.Sleep(Interval); 
} 

intervalo se pasa como un parámetro a la clase por lo que puede pasar sólo en 0 o 1, pero yo estaba interesado en cuanto a la forma de burlarse del reloj del sistema si este no era el caso.

En mi prueba me gustaría poder simplemente configurar el tiempo en TimeSpan Interval y hacer que el hilo se active.

Nunca escribí pruebas para el código que actúa sobre el subproceso que se ejecuta antes y estoy seguro de que hay algunos inconvenientes que evitar. Por favor, siéntase libre de explicar en qué enfoque utiliza.

Gracias!

+0

corto de cambiar realmente el valor del reloj (que estoy seguro de que no quiere hacer), no lo hará ser capaz de obtener los resultados que está buscando. –

+2

La solución preferida es aislar la funcionalidad de sincronización con un servicio para que pueda simular el servicio para sus pruebas. Si no puede hacer esto (a menudo porque es una base de código existente), entonces una alternativa es usar un marco como Moles para desviar las llamadas estáticas: http://research.microsoft.com/en-us/projects/moles/ –

Respuesta

15

Si no desea probar el hecho de que el hilo realmente duerme, un enfoque más directo (y uno que es posible) es tener un ISleepService. Puede simular esto y luego no dormir en sus pruebas, pero tiene una implementación que causa un Thread.Sleep en su código de producción.

ISleepService sleepService = Container.Resolve<ISleepService>(); 

.. 

while (running) 
{ 
    ... 
    // Work 
    ... 
    sleepService.Sleep(Interval); 
} 

Ejemplo usando Moq:

public interface ISleepService 
    { 
     void Sleep(int interval); 
    } 

    [Test] 
    public void Test() 
    { 
     const int Interval = 1000; 

     Mock<ISleepService> sleepService = new Mock<ISleepService>(); 
     sleepService.Setup(s => s.Sleep(It.IsAny<int>())); 
     _container.RegisterInstance(sleepService.Object); 

     SomeClass someClass = _container.Resolve<SomeClass>(); 
     someClass.DoSomething(interval: Interval); 

     //Do some asserting. 

     //Optionally assert that sleep service was called 
     sleepService.Verify(s => s.Sleep(Interval)); 
    } 

    private class SomeClass 
    { 
     private readonly ISleepService _sleepService; 

     public SomeClass(IUnityContainer container) 
     { 
      _sleepService = container.Resolve<ISleepService>(); 
     } 

     public void DoSomething(int interval) 
     { 
      while (true) 
      { 
       _sleepService.Sleep(interval); 
       break; 
      } 
     } 
    } 

actualización

En una nota de diseño \ mantenimiento, si es doloroso para cambiar el constructor de "SomeClass", o para agregar la inyección de dependencias señala al usuario de la clase, a continuación, un patrón de tipo de localizador de servicio puede ayudar aquí, por ejemplo:

private class SomeClass 
{ 
    private readonly ISleepService _sleepService; 

    public SomeClass() 
    { 
     _sleepService = ServiceLocator.Container.Resolve<ISleepService>(); 
    } 

    public void DoSomething(int interval) 
    { 
     while (true) 
     { 
      _sleepService.Sleep(interval); 
      break; 
     } 
    } 
} 
+0

Fantástico - esto tiene mucho sentido - Moq estaba definitivamente en la hoja de ruta también, por lo que su respuesta ha proporcionado una iniciación fácil. – gav

2

No se puede burlar del reloj del sistema.

Si necesita poder modificar el comportamiento de suspensión de un código como este, tendrá que refactorizarlo para que no llame directamente al Thread.Sleep().

Crearía un servicio singleton, que podría ser inyectado en la aplicación cuando esté bajo prueba. El servicio singleton debería incluir métodos para permitir que una persona que llama externa (como una prueba de unidad) pueda cancelar una operación de suspensión.

Como alternativa, puede utilizar un método o WaitHandle del objeto WaitOne() que tiene un parámetro de tiempo de espera excedido. De esta forma podría desencadenar el mutex para cancelar el "sueño" o dejar que el tiempo de espera:

public WaitHandle CancellableSleep = new WaitHandle(); // publicly available 

// in your code under test use this instead of Thread.Sleep()... 
while(running) { 
    // .. work .. 
    CancellableSleep.WaitOne(Interval); // suspends thread for Interval timeout 
} 


// external code can cancel the sleep by doing: 
CancellableSleep.Set(); // trigger the handle... 
+1

Un WaitHandle es una forma muy pesada (en términos de recursos del sistema y rendimiento) de proporcionar una alternativa a 'Thread.Sleep()'.No recomendaría un WaitHandle a menos que tenga otro código que pueda proporcionar una guía sobre cuándo se debe activar el hilo. –

+1

+1 por no poder simular el reloj del sistema. – Larsenal

+1

Bueno, en realidad podría usar la interceptación de llamadas del sistema, pero eso definitivamente sería excesivo. –