c# visual Unidad probando una clase que usa un temporizador




timers timer vb net (2)

Tengo una clase que tiene un miembro privado que tiene para tipo System.Windows.Forms.Timer . También hay un método privado que se está llamando cada vez que mi temporizador marca.

  1. ¿Vale la pena probar el método? (ya que es privado)
  2. ¿Cómo puedo probarlo? (Sé que puedo hacer que mi clase de prueba herede la clase que quiero probar ...)
  3. ¿Debería burlarme de mi temporizador? Porque si tengo que probar una clase que usa un temporizador interno, mis pruebas pueden tomar mucho tiempo en completarse, ¿verdad?

editar:

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 puede ver, si el temporizador se está ejecutando, llama a Display.execute() con diferentes parámetros desde el momento en que finaliza (cuando el tiempo restante es igual a 0). ¿Sería eso un problema de diseño?


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

Tu objetivo es decidir si tu 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.

Además, cuando está realizando pruebas unitarias, la devolución de llamada asignada al evento transcurrido del temporizador es alcanzable si puede simular el temporizador.

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

Puede usar una clase de adaptador aquí. 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
}

Luego puede implementar esta interfaz en una clase de adaptador, simplemente heredando de la clase Timer.

public class TimerAdaper : Timer, ITimer { }

Debes inyectar tu abstracción en el constructor (o como una propiedad) para que puedas simularlo en tus pruebas.

public class MyClass
{
    private readonly ITimer _timer;

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

¿Debería burlarme de mi temporizador? Porque si tengo que probar una clase que usa un temporizador interno, mis pruebas pueden tomar mucho tiempo en completarse, ¿verdad?

Por supuesto, deberías burlarte de tu 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.


  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 ejemplo a continuación: 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 responsabilidades únicas .

Entonces, la programación va al programador :) API del programador podría ser como:

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

Olvídate del planificador por ahora. Regrese e implemente su segunda clase, que mostrará algunas advertencias. Vamos a probar primero (con Moq):

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

Aplicación de escritura:

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

Pases de prueba Escribe 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));
}

Prueba fallida Ah, necesitas condiciones 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 continuar escribiendo pruebas para su clase, que es responsable de manejar las alertas del programador y ejecutar algunas advertencias en 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.





tdd