14

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

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

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

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

4

4 回答 4

7

您必须有某种方法来伪造任务创建。

如果您将Task.Factory.StartNew调用移至某个依赖项 ( ILongRunningOperationStarter),那么您可以创建一个替代实现,用于TaskCompletionSource创建在您希望它们完成的位置完成的任务。

它可能会有点毛茸茸,但可以做到。不久前我在博客上写过这个——单元测试一个接收任务的方法,这当然让事情变得更容易。它在 C# 5 中的 async/await 上下文中,但适用相同的原则。

如果您不想伪造整个任务创建,您可以替换任务工厂,并以这种方式控制时间 - 但老实说,我怀疑这会更麻烦。

于 2012-05-11T18:02:36.380 回答
5

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

 private TaskScheduler taskScheduler;

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

在您的单元测试中,您可以使用本博文中描述的 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
    ...
}
于 2014-01-02T16:07:18.163 回答
0

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

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


Task.Factory.ContinueWhenAll(new Task[] { t }, () => 
{
   Debug.Assert(result != null);
});
于 2012-05-11T19:42:10.033 回答
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>>() ) );
}
于 2017-06-03T11:17:35.057 回答