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

С# Unit Testing - Thread.Sleep(x) - Как отладить системные часы

Мне нужно проверить метод, выполняющий определенный объем работы после интервала.

while (running)
{
    ...
    // Work
    ...
    Thread.Sleep(Interval);
}

Интервал передается как параметр для класса, поэтому я могу просто передать 0 или 1, но мне было интересно, как издеваться над системными часами, если это не так.

В моем тесте я хотел бы просто настроить время вперед по интервалу TimeSpan и прокрутить поток.

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

Спасибо!

4b9b3361

Ответ 1

Если вы не хотите проверять факт, что поток действительно спит, более простой подход (и тот, который возможен) - это иметь ISleepService. Затем вы можете издеваться над этим, а затем не спать в своих тестах, но иметь реализацию, которая вызывает Thread.Sleep в вашем производственном коде.

ISleepService sleepService = Container.Resolve<ISleepService>();

..

while (running)
{
    ...
    // Work
    ...
    sleepService.Sleep(Interval);
}

Пример использования Moq:

    public interface ISleepService
    {
        void Sleep(int interval);
    }

    [Test]
    public void Test()
    {
        const int Interval = 1000;

        Mock<ISleepService> sleepService = new Mock<ISleepService>();
        sleepService.Setup(s => s.Sleep(It.IsAny<int>()));
        _container.RegisterInstance(sleepService.Object);

        SomeClass someClass = _container.Resolve<SomeClass>();
        someClass.DoSomething(interval: Interval);

        //Do some asserting.

        //Optionally assert that sleep service was called
        sleepService.Verify(s => s.Sleep(Interval));
    }

    private class SomeClass
    {
        private readonly ISleepService _sleepService;

        public SomeClass(IUnityContainer container)
        {
            _sleepService = container.Resolve<ISleepService>();
        }

        public void DoSomething(int interval)
        {
            while (true)
            {
                _sleepService.Sleep(interval);
                break;
            }
        }
    }

Обновление

В примечании о дизайне\техобслуживании, если больно изменять конструктор "SomeClass" или добавлять точки инжекции зависимостей к пользователю класса, тогда здесь может помочь шаблон типа локатора сервисов, например:

private class SomeClass
{
    private readonly ISleepService _sleepService;

    public SomeClass()
    {
        _sleepService = ServiceLocator.Container.Resolve<ISleepService>();
    }

    public void DoSomething(int interval)
    {
        while (true)
        {
            _sleepService.Sleep(interval);
            break;
        }
    }
}

Ответ 2

Вы не можете высмеивать системные часы.

Если вам нужно изменить существующее поведение кода, как это, вам нужно будет реорганизовать его так, чтобы вы не вызывали Thread.Sleep() напрямую.

Я бы создал однопользовательский сервис, который можно было бы ввести в приложение, когда он будет тестироваться. Служба singleton должна включать методы, позволяющие некоторым внешним вызывающим абонентам (например, unit test) отменить операцию сна.

В качестве альтернативы вы можете использовать метод Mutex или WaitHandle object WaitOne(), который имеет параметр таймаута. Таким образом, вы можете вызвать мьютекс, чтобы отменить "спящий" или дать ему время ожидания:

public WaitHandle CancellableSleep = new WaitHandle(); // publicly available

// in your code under test use this instead of Thread.Sleep()...
while( running ) {
    // .. work ..
    CancellableSleep.WaitOne( Interval ); // suspends thread for Interval timeout
}


// external code can cancel the sleep by doing:
CancellableSleep.Set(); // trigger the handle...