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

Модульное тестирование асинхронной работы

Я хочу unit test метод, который у меня есть, который выполняет и асинхронную операцию:

 Task.Factory.StartNew(() =>
        {
            // method to test and return value
            var result = LongRunningOperation();
        });

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

Как я могу обойти это? Должен ли я создать макет TaskFactory или любые другие советы для тестирования асинхронной работы?

4b9b3361

Ответ 1

У вас должен быть какой-то способ подделать создание задачи.

Если вы переместили вызов Task.Factory.StartNew на некоторую зависимость (ILongRunningOperationStarter), вы могли бы создать альтернативную реализацию, которая использовала TaskCompletionSource для создания задач, которые полностью соответствуют тому, где вы хотите.

Это может стать немного волосатым, но это можно сделать. я писал об этом некоторое время назад - модульное тестирование метода, с которого начинались задачи, что, конечно же, упростило ситуацию. Это в контексте async/await в С# 5, но применяются те же принципы.

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

Ответ 2

Я бы предложил заглушить TaskScheduler в вашем методе со специальной реализацией для модульных тестов. Вам нужно подготовить свой код для использования введенного TaskScheduler:

 private TaskScheduler taskScheduler;

 public void OperationAsync()
 {
     Task.Factory.StartNew(
         LongRunningOperation,
         new CancellationToken(),
         TaskCreationOptions.None, 
         taskScheduler);
 }

В вашем unit test вы можете использовать DefinisticTaskScheduler, описанный в этом сообщении в блоге, чтобы запустить новую задачу в текущем потоке. Ваша операция async будет закончена, прежде чем вы нажмете свой первый оператор assert:

[Test]
public void ShouldExecuteLongRunningOperation()
{
    // Arrange: Inject task scheduler into class under test.
    DeterministicTaskScheduler taskScheduler = new DeterministicTaskScheduler();
    MyClass mc = new MyClass(taskScheduler);

    // Act: Let async operation create new task
    mc.OperationAsync();
    // Act:  Execute task on the current thread.
    taskScheduler.RunTasksUntilIdle();

    // Assert
    ...
}

Ответ 3

Попробуйте что-нибудь подобное...

object result = null;
Task t =  Task.Factory.StartNew(() => result = LongRunningThing()); 


Task.Factory.ContinueWhenAll(new Task[] { t }, () => 
{
   Debug.Assert(result != null);
});

Ответ 4

Задайте графики пользовательских интерфейсов и фоновых задач и замените их на unit test на этот.

Ниже код был скопирован из Интернета, извините за отсутствующую ссылку на автора:

  public class CurrentThreadTaskScheduler : TaskScheduler
  {
    protected override void QueueTask(Task task)
    {
      TryExecuteTask(task);
    }

    protected override bool TryExecuteTaskInline(
       Task task,
       bool taskWasPreviouslyQueued)
    {
      return TryExecuteTask(task);
    }

    protected override IEnumerable<Task> GetScheduledTasks()
    {
      return Enumerable.Empty<Task>();
    }

    public override int MaximumConcurrencyLevel => 1;
  }

Итак, чтобы проверить код:

   public TaskScheduler TaskScheduler
    {
      get { return taskScheduler ?? (taskScheduler = TaskScheduler.Current); }
      set { taskScheduler = value; }
    }

    public TaskScheduler TaskSchedulerUI
    {
      get { return taskSchedulerUI ?? (taskSchedulerUI = TaskScheduler.FromCurrentSynchronizationContext()); }
      set { taskSchedulerUI = value; }
    }
  public Task Update()
    {
      IsBusy = true;
      return Task.Factory.StartNew( () =>
                 {
                   LongRunningTask( );
                 }, CancellationToken.None, TaskCreationOptions.None, TaskScheduler )
                 .ContinueWith( t => IsBusy = false, TaskSchedulerUI );
    }

Вы напишете следующий unit test:

[Test]
public void WhenUpdateThenAttributeManagerUpdateShouldBeCalled()
{
  taskScheduler = new CurrentThreadTaskScheduler();
  viewModel.TaskScheduler = taskScheduler;
  viewModel.TaskSchedulerUI = taskScheduler;
  viewModel.Update();
  dataManagerMock.Verify( s => s.UpdateData( It.IsAny<DataItem>>() ) );
}