2012-07-15 12 views
29

tengo una clase que tiene un miembro privada que tiene para el tipo System.Windows.Forms.Timer. También hay un método privado que se está llamando cada vez que mi temporizador marca.Unidad de prueba de una clase que utiliza un temporizador

  1. ¿Vale la pena probar el método? (ya que es privado)
  2. ¿Cómo puedo probarlo? (Sé que puedo tener mi clase de prueba que hereda la clase quiero probar ...)
  3. ¿Debo estar burlándose de mi contador? Porque si tengo que probar una clase que usa un temporizador interno, mis pruebas pueden tomar mucho tiempo en completarse, ¿verdad?

edición:

En realidad, el método tiene una dependencia en el tiempo, aquí está el código:

private void alertTick(object sender, EventArgs e) { 
    if (getRemainingTime().Seconds <= 0) { 
     Display.execute(Name, WarningState.Ending, null); 
     AlertTimer.Stop(); 
    } 
    else { 
     var warning = _warnings.First(x => x == getRemainingTime()); 

     if (warning.TotalSeconds > 0) 
      Display.execute(Name, WarningState.Running, warning); 
    } 
} 

Como se puede ver, si el temporizador está en marcha, se llama Display.execute() con diferentes parámetros desde cuando termina (cuando el tiempo restante es igual a 0). ¿Sería eso un problema de diseño?

+0

¿Qué comportamiento desea verificar? –

+2

¿El método real en sí tiene alguna dependencia en el tiempo? Su prueba de unidad debe probar la funcionalidad del método; el hecho de que se llame con regularidad no debe alterar el hecho de que desea probar que el método funciona. Diría que definitivamente no hay necesidad de burlarse de un temporizador a menos que el éxito del método dependa del tiempo. El acceso de un método no debería importar cuando se trata de pruebas; es una parte funcional de tu programa. – dash

+0

+1 porque estaba escribiendo exactamente la misma pregunta para las pruebas de campos privados, me ganaste a tiempo – HatSoft

Respuesta

29
  1. No está probando métodos (privados o públicos) - está verificando el comportamiento de su clase.Y si no ha verificado algún comportamiento, entonces no puede decir que se implementó. Hay varias maneras en que se puede invocar este comportamiento: interfaz pública de su clase o algún evento en dependencia. Además, no es necesario que la invocación de comportamiento cambie algo alcanzado por la interfaz pública, las interacciones con las dependencias también importan.
  2. Vea el siguiente ejemplo: muestra cómo probar dicho comportamiento "oculto".
  3. Vea el ejemplo a continuación: muestra cómo dividir responsabilidades, inyectar dependencias y simularlas.

En realidad, su clase tiene demasiadas responsabilidades, una está programando una tarea y otra está ejecutando algunas acciones. Intenta dividir tu clase en dos clases separadas con single responsibilities.

Así, la programación va a planificador :) API de programador podría ser como:

public interface IScheduler 
{ 
    event EventHandler<SchedulerEventArgs> Alarm; 
    void Start(); 
    void Stop(); 
} 

Olvídate de planificador por ahora. Regrese e implemente su segunda clase, que mostrará algunas advertencias. Vayamos a la primera prueba (con Moq): aplicación

[Test] 
public void ShouldStopDisplayingWarningsWhenTimeIsOut() 
{ 
    Mock<IDisplay> display = new Mock<IDisplay>(); 
    Mock<IScheduler> scheduler = new Mock<IScheduler>();      

    Foo foo = new Foo("Bar", scheduler.Object, display.Object); 
    scheduler.Raise(s => s.Alarm += null, new SchedulerEventArgs(0)); 

    display.Verify(d => d.Execute("Bar", WarningState.Ending, null)); 
    scheduler.Verify(s => s.Stop()); 
} 

Comentario:

public class Foo 
{ 
    private readonly IScheduler _scheduler; 
    private readonly IDisplay _display; 
    private readonly string _name; 

    public Foo(string name, IScheduler scheduler, IDisplay display) 
    { 
     _name = name; 
     _display = display; 
     _scheduler = scheduler; 
     _scheduler.Alarm += Scheduler_Alarm; 
     _scheduler.Start(); 
    } 

    private void Scheduler_Alarm(object sender, SchedulerEventArgs e) 
    { 
     _display.Execute(_name, WarningState.Ending, null); 
     _scheduler.Stop(); 
    } 
} 

pasa la prueba. Escriba otro:

[Test] 
public void ShouldNotStopDisplayingWarningsWhenTimeRemains() 
{ 
    Mock<IDisplay> display = new Mock<IDisplay>(MockBehavior.Strict); 
    Mock<IScheduler> scheduler = new Mock<IScheduler>(MockBehavior.Strict); 
    scheduler.Setup(s => s.Start()); 

    Foo foo = new Foo("Bar", scheduler.Object, display.Object); 
    scheduler.Raise(s => s.Alarm += null, new SchedulerEventArgs(1)); 
} 

Error de prueba. Ah, es necesario condición para el tiempo restante:

private void Scheduler_Alarm(object sender, SchedulerEventArgs e) 
{ 
    if (e.RemainingTime > 0) 
     return; 

    _display.Execute(_name, WarningState.Ending, null); 
    _scheduler.Stop(); 
} 

Puede seguir las pruebas de escritura para su clase, la cual responsable de atender las alertas planificador y ejecutar algunas advertencias en la pantalla. Cuando termine, puede escribir la implementación para su interfaz IScheduler. No importa cómo implementará la programación, a través de System.Windows.Forms.Timer o a través de System.ThreadingTimer, o de alguna otra manera.

19

¿Vale la pena probar el método? (ya que es privado)

Su propósito es decidir si su código funciona o no. Incluso es un método privado, debe generar un resultado al que pueda acceder la interfaz pública. Debe diseñar su clase de manera que el usuario pueda saber si funciona o no.

también cuando se está probando la unidad, la devolución de llamada asignado al evento transcurrido del temporizador es alcanzable si se puede burlarse del temporizador.

¿Cómo se puede probar que es? (Sé que puedo hacer que mi clase de prueba herede la clase que quiero probar ...)

Aquí puede usar una clase de adaptador. Primero debes definir una abstracción ya que la clase Timer no ofrece una.

public interface ITimer 
{ 
    void Start(); 
    void Stop(); 
    double Interval { get; set; } 
    event ElapsedEventHandler Elapsed; 
    //and other members you need 
} 

Entonces se puede implementar esta interfaz en una clase adaptador, simplemente que hereda de la clase Timer.

public class TimerAdaper : Timer, ITimer { } 

debe inyectarse la abstracción en el constructor (o como una propiedad) para que pueda burlarse en sus pruebas.

public class MyClass 
{ 
    private readonly ITimer _timer; 

    public MyClass(ITimer timer) 
    { 
     _timer = timer 
    } 
} 

¿Debo burlarse de mi contador? Porque si tengo que probar una clase que usa un temporizador interno, mis pruebas pueden llevar mucho tiempo en completarse, ¿no? ¿verdad?

Por supuesto, debe burlarse de su temporizador. Las pruebas de su unidad no pueden depender del tiempo del sistema. Debería plantear eventos burlándose y ver cómo se comporta su código.

+0

por favor, puede elaborar si vale la pena probar Test privado y cómo probarlos – HatSoft

+3

@HatSoft Si una parte de la lógica (pública o privada) no puede ser alcanzada por la interfaz pública de una clase, el código está muerto (no se puede usar). Entonces, siempre deberíamos poder probar todo el código en la clase. –

+0

@HatSoft Incluso el código adjunto al evento transcurrido del temporizador es privado, la parte donde se inicia debería estar en algún miembro público (tal vez el constructor). Eso hace que la parte privada sea accesible. Sin un contexto, no puedo decir cómo puedes probarlo, pero debes y puedes probarlo. –

Cuestiones relacionadas