单元测试异步操作

时间:2012-05-11 17:59:42

标签: c# unit-testing testing asynchronous

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

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

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

我该如何解决这个问题?我应该创建TaskFactory的模拟或任何其他单元测试异步操作的技巧吗?

4 个答案:

答案 0 :(得分:7)

你必须有一些伪造任务创造的方法。

如果您将Task.Factory.StartNew调用移动到某个依赖项(ILongRunningOperationStarter),那么您可以创建一个替代实现,该实现使用TaskCompletionSource来创建完全完成您所需位置的任务。< / p>

它可能有点毛茸茸,但可以完成。我blogged about this前一段时间 - 单元测试接收任务的方法,这当然使事情变得更容易。它是在C#5中的异步/等待的上下文中,但适用相同的原则。

如果你不想伪造整个任务创建,你可以更换任务工厂,并控制时间 - 但我怀疑这将更加毛茸茸,说实话。

答案 1 :(得分:4)

我建议在你的方法中使用特殊的单元测试实现存根TaskScheduler。您需要准备代码以使用注入的TaskScheduler:

 private TaskScheduler taskScheduler;

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

在单元测试中,您可以使用this blog post中描述的DeterministicTaskScheduler在当前线程上运行新任务。在您点击第一个断言语句之前,您的“异步”操作将完成:

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

答案 2 :(得分:0)

尝试这样的事情......

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


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

答案 3 :(得分: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>>() ) );
}