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