unit testing异步操作

我想unit testing我执行的方法和异步操作:

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

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

我怎么能绕过这个? 我应该创建TaskFactory的模拟或任何其他unit testing异步操作的技巧吗?

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

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

它可能会有点毛茸茸,但它可以做到。 我刚才在博客上写了一篇文章 – unit testing一个接收任务的方法,这当然使事情变得更容易。 它是在C#5中的异步/等待的上下文中,但同样的原则适用。

如果你不想伪造整个任务创建,你可以替换任务工厂,并控制时间 – 但我怀疑,甚至更加毛茸茸。

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

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

在unit testing中,您可以使用此博客文章中描述的DeterministicTaskScheduler在当前线程上运行新任务。 在执行第一个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 ... } 

试试这样的事……

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

设置UI和后台任务计划,并在unit testing中用这个替换它们。

下面的代码是从互联网上复制的,遗憾的是缺少对作者的参考:

  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 GetScheduledTasks() { return Enumerable.Empty(); } 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 testing:

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