异步操作的单元测试

14

我想对一个执行异步操作的方法进行单元测试:

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

我在我的单元测试(用c#编写)中存根了必要的方法等,但问题是异步操作在我做断言测试之前没有完成。

我该怎么解决?我应该创建TaskFactory的模拟对象还是有其他提示可以用来对异步操作进行单元测试?

4个回答

7

您需要有一种方法来模拟任务创建。

如果将Task.Factory.StartNew调用移动到某个依赖项(ILongRunningOperationStarter),则可以创建一个替代实现,该实现使用TaskCompletionSource创建任务,这些任务在您希望它们完成的地方完成。

这可能有点棘手,但是确实可以做到。 我之前写过博客 - 测试方法接收要开始的任务,当然也会使事情变得更容易。 它是在C# 5中异步/等待的上下文中,但是相同的原则适用。

如果您不想完全模拟整个任务创建过程,则可以替换任务工厂,并以这种方式控制时间 - 但是我认为这将更加困难。


@luviktor:已修复,谢谢。 (下次您可以自己建议编辑。) - Jon Skeet

5

我建议在你的方法中桩起一个TaskScheduler,并使用专门为单元测试实现的特殊实现。你需要准备好你的代码以使用注入的TaskScheduler:

 private TaskScheduler taskScheduler;

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

在您的单元测试中,您可以使用文章中描述的DeterministicTaskScheduler在当前线程上运行新任务。在您第一次assert语句之前,您的“async”操作将完成:

[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
    ...
}

0

设置UI和后台任务调度程序,并在单元测试中使用此方法替换它们。

下面的代码是从互联网上复制的,抱歉没有提供作者的引用:

  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 );
    }

你将编写以下单元测试:
[Test]
public void WhenUpdateThenAttributeManagerUpdateShouldBeCalled()
{
  taskScheduler = new CurrentThreadTaskScheduler();
  viewModel.TaskScheduler = taskScheduler;
  viewModel.TaskSchedulerUI = taskScheduler;
  viewModel.Update();
  dataManagerMock.Verify( s => s.UpdateData( It.IsAny<DataItem>>() ) );
}

0

尝试像这样做...

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


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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接