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

Код модульного тестирования, который использует Task.Factory.StartNew(). ContinueWith()

поэтому у меня есть код

Task.Factory.StartNew(() => this.listener.Start()).ContinueWith(
                    (task) =>
                        {
                            if (task.IsCompleted)
                            {
                                this.status = WorkerStatus.Started;
                                this.RaiseStatusChanged();
                                this.LogInformationMessage("Worker Started.");
                            }
                        });

Когда я тестирую, я издеваюсь над всеми зависимыми объектами (namley this.listener.Start()). проблема в том, что тест заканчивается, прежде чем ContinueWith можно вызвать. Когда я отлаживаю его, он получает штраф из-за дополнительной задержки, когда я перехожу через код.

так как я могу - из тестового кода в другой сборке - убедиться, что код запущен до того, как мой тест попадает в его утверждения?

Я мог бы просто использовать Thread.Sleep... но это похоже на действительно хакерский способ сделать это.

Я думаю, что я ищу версию задачи Thread.Join.

4b9b3361

Ответ 1

Рассмотрим следующее:

public class SomeClass
{
    public void Foo()
    {
        var a = new Random().Next();
    }
}

public class MyUnitTest
{
    public void MyTestMethod()
    {
        var target = new SomeClass();        
        target.Foo(); // What to assert, what is the result?..
    }
}

Каково значение, присвоенное a? Вы не можете сказать, если результат не возвращается за пределами метода Foo() (как возвращаемое значение, общедоступное свойство, событие и т.д.).

Процесс координирует действия потоков для прогнозируемого результата "называется Синхронизация.

Одним из самых простых решений в вашем случае может быть возврат экземпляра класса Task и использование его Wait():

var task = Task.Factory.StartNew(() => Method1())
    .ContinueWith(() => Method2());

Не нужно ждать первой задачи, потому что ContinueWith() создает продолжение, которое выполняется асинхронно , когда целевая задача завершается (MSDN):

task.Wait();

Ответ 2

Я не думаю, что есть простой, но практичный способ сделать это. Ранься в ту же проблему, что и сейчас, и Thread.Sleep(X) - самый простой (если не элегантный) способ обойти проблему.

Единственное другое решение, которое я рассмотрел, скрывает вызов Task.Factory.StartNew() за интерфейсом, который вы можете издеваться над своим тестом, тем самым полностью исключая фактическое выполнение задачи в тестовом сценарии (но все же есть ожидание что будет вызван метод интерфейса. Например:

public interface ITaskWrapper
{
    void TaskMethod();
}

И ваша конкретная реализация:

public class MyTask : ITaskWrapper
{
    public void TaskMethod()
    {
        Task.Factory.StartNew(() => DoSomeWork());
    }
}

Затем просто выкачайте ITaskWrapper в свой тестовый метод и назначьте ожидание при вызове TaskMethod.

Ответ 3

Если вы хотите получить уведомление о завершении обработки (можете ли вы добавить обработчик для этого события StatusChanged?), используйте ManualResetEvent и подождите его с разумным таймаутом. Если истечение таймаута истекло, пропустите тест, в противном случае продолжайте выполнение и выполните свои утверждения.

например.

var waitHandle = new ManualResetEvent(false);
sut.StatusChanged += (s, e) => waitHandle.Set();

sut.DoStuff();

Assert.IsTrue(waitHandle.WaitOne(someTimeout), "timeout expired");
// do asserts here

Ответ 4

Задача продолжения будет выполняться независимо от того, была ли начальная задача выполнена до вызова ContinueWith() или нет. Я дважды проверил это со следующим:

// Task immediately exits
var task = Task.Factory.StartNew(() => { });

Thread.Sleep(100);

// Continuation on already-completed task
task.ContinueWith(t => { MessageBox.Show("!"); });

Отладка. Возможно, ваша задача не работает.

Ответ 5

При работе с асинхронными процессами во время тестируемого кода, использующих Reactive Extensions, один подход заключается в использовании TestScheduler. TestScheduler можно перемещать вперед вовремя, сбрасывать все запущенные задачи и т.д. Таким образом, ваш тестируемый код может использовать IScheduler, для которого вы предоставляете экземпляр TestScheduler. Тогда ваш тест может манипулировать временем без необходимости фактически спать, ждать или синхронизировать. Улучшением этого подхода является подход Ли Кэмпбелла ISchedulerProvider.

Если вы используете Observable.Start вместо Task.Factory.StartNew в своем коде, вы можете использовать свой TestScheduler в unit test для выполнения всех запланированных задач.

Например, ваш тестируемый код может выглядеть примерно так:

//Task.Factory.StartNew(() => DoSomething())
//    .ContinueWith(t => DoSomethingElse())
Observable.Start(() => DoSomething(), schedulerProvider.ThreadPool)
          .ToTask()
          .ContinueWith(t => DoSomethingElse())

и в unit test:

// ... test code to execute the code under test

// run the tasks on the ThreadPool scheduler
testSchedulers.ThreadPool.Start();

// assertion code can now run