UnitTesting многопоточный класс, избегая Thread.Sleep () в тесте? - PullRequest
5 голосов
/ 08 июля 2011

Я пытаюсь найти лучший способ для юнит-тестирования этого класса:

public class FileGroupGarbageCollector
{
    private Task _task;

    private readonly AutoResetEvent _event = new AutoResetEvent(false);

    public void Start()
    {
        _task = Task.Factory.StartNew(StartCollecting);

    }

    public void Stop()
    {
        _event.Set();
    }

    private void StartCollecting()
    {
        do
        {
            Process();
        }
        while (!_event.WaitOne(60000, false));            
    }

    private void Process()
    {
        /* do some work to the database and file system */
    }
}

Это не самый хорошо сформированный класс, просто пытаюсь что-то выяснить!

Затем у меня есть модульный тест, в котором я хочу запустить, а затем остановить службу, утверждая, что приватный метод 'Процессы' сделал что-то с базой данных или файловой системой.

Мой юнит-тест выглядит следующим образом (nunit):

    [Test]
    public void TestStart()
    {
        var fg = new FileGroupGarbageCollector(30000);

        fg.Start();

        Thread.Sleep(5000); // i hate this!

        fg.Stop();

        // assert it did what i wanted it to do!
    }

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

Любые ответы с благодарностью:)

ОБНОВЛЕНИЕ ПОСЛЕ ОТВЕТА

Я пошел по пути IoC, и он работает очень хорошо:)

открытый интерфейс IEventFactory { IEvent Create (); }

public interface IEvent
{
    bool WaitOne(int timeout);
    void Set();
}

Тогда мои фиктивные объекты (используя Moq):

 var mockEvent = new Mock<IEvent>();
 var mockEventFactory = new Mock<IEventFactory>();

 mockEvent.Setup(x => x.WaitOne(It.IsAny<int>())).Returns(true);
 mockEvent.Setup(x => x.Set());

 mockEventFactory.Setup(x => x.Create()).Returns(mockEvent.Object);

Итак, вызов IEvent.WaitOne () мгновенно возвращает true и завершается, поэтому нет необходимости в Thread.Sleep ()!

:)

Ответы [ 2 ]

7 голосов
/ 08 июля 2011

По сути, здесь вы должны применить шаблон Inversion of Control.Поскольку код тесно связан, у вас возникли проблемы с его тестированием.

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

public interface ITaskFactory {}
public interface IThreadManager {}
public interface ICollectorDatabase {}
public interface IEventFactory {} 

public class FileGroupGarbageCollector 
{
  ITaskFactory taskFactory;
  IThreadManager threadManager;
  ICollectorDatabase database;
  IEventFactory events;

  FileGroupGarbageCollector (ITaskFactory taskFactory,
    IThreadManager threadManager, ICollectorDatabase database,
    IEventFactory events)
  {
     // init members..
  }
}

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

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

1 голос
/ 08 июля 2011

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

Единственный другой вариант - изменить использование «времени». Команда Rx проделала большую работу в этой области; все их планировщики проверяемы . Но это не поможет вашей конкретной ситуации (если вы не конвертируетесь в планировщики Rx).

Если вы действительно хотите избежать Thread.Sleep в своих модульных тестах, то вам нужно абстрагироваться от частей, которые зависят от времени (либо с помощью Inversion of Control, либо с помощью библиотеки перехвата, например Microsoft Moles); проблема в том, что очень сложно создать полную и последовательную абстракцию «времени». Лично я не теряю сон из-за наличия Thread.Sleep в моих модульных тестах.

...