web-dev-qa-db-fra.com

Test unitaire d'une classe utilisant un Timer

J'ai une classe qui a un membre privé qui a pour type System.Windows.Forms.Timer. Il y a aussi une méthode privée qui est appelée à chaque fois que mon chronomètre se déclenche.

  1. Vaut-il la peine de tester la méthode? (puisque c'est privé)
  2. Comment puis-je le tester? (Je sais que ma classe de test peut hériter de la classe que je veux tester ...)
  3. Dois-je me moquer de ma minuterie? Parce que si je dois tester une classe qui utilise une minuterie interne, mes tests peuvent prendre beaucoup de temps, non?

éditer:

En fait, la méthode dépend du timing, voici le code:

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

Comme vous pouvez le voir, si le temporisateur est en cours d'exécution, il appelle Display.execute() avec des paramètres différents de sa fin (lorsque le temps restant est égal à 0). Serait-ce un problème de conception?

37
Pacane
  1. Vous ne testez pas les méthodes (privées ou publiques) - vous vérifiez le comportement de votre classe. Et si vous n'avez pas vérifié un comportement, vous ne pouvez pas dire qu'il a été mis en œuvre. Il existe plusieurs façons d'invoquer ce comportement: interface publique de votre classe ou événement en dépendance. Il n'est pas non plus nécessaire que l'invocation de comportement change quelque chose atteint par l'interface publique, les interactions avec les dépendances sont également importantes.
  2. Voir l'exemple ci-dessous - il montre comment tester un tel comportement "caché".
  3. Voir l'exemple ci-dessous - il montre comment répartir les responsabilités, injecter les dépendances et les simuler.

En fait, votre classe a trop de responsabilités - l'une planifie une tâche et l'autre - exécute certaines actions. Essayez de diviser votre classe en deux classes distinctes avec responsabilités uniques .

Ainsi, la planification va au planificateur :) L'API du planificateur pourrait ressembler à:

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

Oubliez le planificateur pour l'instant. Retournez et implémentez votre deuxième classe, qui affichera quelques avertissements. Allons tester d'abord (avec 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());
}

Ecrire l'implémentation:

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

Le test réussit. Écrivez-en un autre:

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

Test échoué. Ah, vous avez besoin de condition pour le temps restant:

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

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

Vous pouvez continuer à écrire des tests pour votre classe, qui est chargée de gérer les alertes du planificateur et d'exécuter certains avertissements à l'écran. Lorsque vous avez terminé, vous pouvez écrire l'implémentation de votre interface IScheduler. Peu importe la façon dont vous implémenterez la planification - via System.Windows.Forms.Timer ou via System.ThreadingTimer, ou d'une autre manière.

38
Sergey Berezovskiy

Vaut-il la peine de tester la méthode? (puisque c'est privé)

Votre but est de décider si votre code fonctionne ou non. Même s'il s'agit d'une méthode privée, elle devrait générer une sortie accessible par l'interface publique. Vous devez concevoir votre classe de manière à ce que l'utilisateur sache si cela fonctionne ou non.

De plus, lorsque vous testez l'unité, le rappel affecté à l'événement Elapsed du minuteur est accessible si vous pouvez vous moquer du minuteur.

Comment puis-je le tester? (Je sais que ma classe de test peut hériter de la classe que je veux tester ...)

Vous pouvez utiliser une classe d'adaptateur ici. Vous devez d'abord définir une abstraction car la classe Timer n'en propose pas.

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

Ensuite, vous pouvez implémenter cette interface dans une classe d'adaptateur, héritant simplement de la classe Timer.

public class TimerAdaper : Timer, ITimer { }

Vous devez injecter votre abstraction dans le constructeur (ou en tant que propriété) afin de pouvoir vous en moquer dans vos tests.

public class MyClass
{
    private readonly ITimer _timer;

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

Dois-je me moquer de ma minuterie? Parce que si je dois tester une classe qui utilise une minuterie interne, mes tests peuvent prendre beaucoup de temps, non?

Bien sûr, vous devriez vous moquer de votre minuterie. Vos tests unitaires ne peuvent pas dépendre de l'heure du système. Vous devez déclencher des événements en vous moquant et voir comment votre code se comporte.

25