Плохо ли юнит-тестирование действий с временным интервалом с 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? Просто проведите блок юнит-теста, пока не получите событие от субъекта (или не проводите непрерывный опрос субъекта). Какой бы метод вы ни использовали.