Плохо ли юнит-тестирование действий с временным интервалом с Thread.Sleep?

Если у меня есть испытуемый объект с таймером, который заставляет выполнять какое-то действие через определенный промежуток времени, каков хороший способ проверить это?

Один из методов заключается в том, чтобы обернуть таймер в интерфейс и внедрить его в качестве зависимости.

Однако я бы хотел избежать создания еще одной абстракции. Кажется, я могу избежать этого, вводя интервал обновления, а не таймер. Затем в моем тесте (при условии, что стиль тестирования ААА), я положил Thread.Sleep после Act и до Assert, используя очень маленькое значение времени, чтобы тест не занял много времени.

Это плохая идея? Я знаю, что это, вероятно, не в полной мере следует принципам TDD, но, похоже, должна быть линия, где вы перестаете окружать все контрактом и вводить его.

2 ответа

Решение

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

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

Простой способ виртуализации времени - использовать что-то вроде этого:

interface ITimeService {

  DateTime Now { get; }

  void Sleep(TimeSpan delay);

}

class TimeService : ITimeService {

  public DateTime Now { get { return DateTime.UtcNow; } }

  public void Sleep(TimeSpan delay) { Thread.Sleep(delay); }

}

class TimeServiceStub : ITimeService {

  DateTime now;

  public TimeServiceStub() {
    this.now = DateTime.UtcNow;
  }

  public DateTime Now { get { return this.now; } }

  public void Sleep(TimeSpan delay) {
    this.now += delay;
  }

}

Вам придется расширить эту идею, если вам требуется более реактивное поведение, например, включение таймеров и т. Д.

Внедрение зависимости - это способ полностью избежать "тестового" кода в вашем рабочем коде (например, установить интервал только для модульного тестирования).

Однако в этом случае я бы использовал заданный интервальный код, но использовал его как в модульных тестах, так и в производстве. Устанавливают его на что угодно, а юнит-тесты устанавливают очень небольшое количество (10 мс?). Тогда у вас не будет никакого мертвого кода, бродящего вокруг в производстве.

Если вы установите интервал, я не понимаю, зачем вам нужен Thread.Sleep? Просто проведите блок юнит-теста, пока не получите событие от субъекта (или не проводите непрерывный опрос субъекта). Какой бы метод вы ни использовали.

Другие вопросы по тегам