Подтвердить что ты не робот

Единичное тестирование класса, использующего таймер

У меня есть класс, у которого есть частный член, который имеет тип System.Windows.Forms.Timer. Существует также частный метод, который вызывается каждый раз, когда мой таймер тикает.

  • Стоит ли тестировать метод? (так как он частный)
  • Как я могу проверить его? (Я знаю, что у меня может быть класс тестирования, наследующий класс, который я хочу проверить...)
  • Должен ли я высмеивать свой таймер? Потому что, если мне нужно протестировать класс, который использует внутренний таймер, мои тесты могут занять много времени, не так ли?

изменить:

На самом деле, метод имеет зависимость от времени, здесь код:

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

Как вы можете видеть, если таймер запущен, он вызывает Display.execute() с разными параметрами с момента его окончания (когда оставшееся время равно 0). Будет ли это проблемой дизайна?

4b9b3361

Ответ 1

  • Вы не тестируете методы (частные или общедоступные) - вы проверяете поведение своего класса. И если вы не проверили какое-либо поведение, то вы не можете сказать, что оно было реализовано. Существует несколько способов вызвать это поведение - открытый интерфейс вашего класса или какое-либо событие в зависимости. Также не обязательно, чтобы поведение вызова изменило значение, достигнутое публичным интерфейсом, также имеет значение взаимодействие с зависимостями.
  • См. пример ниже - он показывает, как тестировать такое "скрытое" поведение.
  • См. пример ниже - он показывает, как разделить обязанности, ввести зависимости и высмеять их.

На самом деле у вашего класса слишком много обязанностей - один из них планирует какую-то задачу, а другой - выполнение некоторых действий. Попробуйте разделить свой класс на два отдельных класса с отдельные обязанности.

Итак, планирование идет в планировщик:) API планировщика может быть следующим:

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

Забудьте о планировщике сейчас. Верните и реализуйте свой второй класс, который отобразит некоторые предупреждения. Сначала отпустите тест (с помощью 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());
}

Запись реализации:

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

Тест проходит. Напишите еще один:

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

Тест завершился неудачно. Ах, вам нужно условие для оставшегося времени:

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

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

Вы можете продолжать писать тесты для своего класса, которые отвечают за обработку предупреждений планировщика и выполнение некоторых предупреждений на дисплее. Когда вы закончите, вы можете написать реализацию для вашего интерфейса IScheduler. Неважно, как вы будете выполнять планирование - через System.Windows.Forms.Timer или через System.ThreadingTimer или каким-либо другим способом.

Ответ 2

Стоит ли тестировать метод? (так как он частный)

Ваша цель - решить, работает ли ваш код или нет. Даже это частный метод, он должен генерировать вывод, который может быть достигнут публичным интерфейсом. Вы должны разработать свой класс таким образом, чтобы пользователь мог знать, работает он или нет.

Также, когда вы тестируете устройство, обратный вызов, назначенный для прошедшего события таймера, доступен, если вы можете издеваться над таймером.

Как я могу проверить его? (Я знаю, что мой тестовый класс может наследовать класс, который я хочу проверить...)

Здесь вы можете использовать класс адаптера. Сначала вы должны определить абстракцию, поскольку класс Timer не предлагает ее.

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

Затем вы можете реализовать этот интерфейс в классе адаптера, просто наследуя от класса Timer.

public class TimerAdaper : Timer, ITimer { }

Вы должны ввести свою абстракцию в конструктор (или как свойство), чтобы вы могли издеваться над ним в своих тестах.

public class MyClass
{
    private readonly ITimer _timer;

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

Должен ли я высмеивать свой таймер? Потому что, если мне нужно протестировать класс, который использует внутренний таймер, мои тесты могут занять много времени, не так ли?

Конечно, ты должен издеваться над своим таймером. Ваши модульные тесты не могут зависеть от системного времени. Вы должны поднять события, насмехаясь и посмотреть, как ведет себя ваш код.