如何使用TPL和TaskScheduler编写单元测试

Dav*_*vid 18 c# unit-testing asynchronous task-parallel-library

想象一下这样的函数:

private static ConcurrentList<object> list = new ConcurrentList<object>();
public void Add(object x)
{
   Task.Factory.StartNew(() =>
   {
      list.Add(x); 
   }
}
Run Code Online (Sandbox Code Playgroud)

我不在乎什么时候确实将fentry添加到列表中,但我需要将它添加到最后(显然;))

我没有看到一种方法来正确地单元测试这样的东西而不返回任何回调处理程序或某事.因此,添加程序不需要的逻辑

你会怎么做?

Jar*_*Par 18

一种方法是使您的类型可配置,以便它需要一个TaskScheduler实例.

public MyCollection(TaskScheduler scheduler) {
  this.taskFactory = new TaskFactory(scheduler);
}

public void Add(object x) {
  taskFactory.StartNew(() => {
    list.Add(x);
  });
}
Run Code Online (Sandbox Code Playgroud)

现在,在您的单元测试中,您可以创建一个可测试版本TaskScheduler.这是一个可配置的抽象类.简单地使用schedule函数将项添加到队列中,然后添加一个函数以"现在"手动执行所有队列项.然后你的单元测试看起来像这样

var scheduler = new TestableScheduler();
var collection = new MyCollection(scehduler);
collection.Add(42);
scheduler.RunAll();
Assert.IsTrue(collection.Contains(42));
Run Code Online (Sandbox Code Playgroud)

示例实现 TestableScehduler

class TestableScheduler : TaskScheduler {
  private Queue<Task> m_taskQueue = new Queue<Task>();

  protected override IEnumerable<Task> GetScheduledTasks() {
    return m_taskQueue;
  }

  protected override void QueueTask(Task task) {
    m_taskQueue.Enqueue(task);
  }

  protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) {
    task.RunSynchronously();
  }

  public void RunAll() {
    while (m_taskQueue.Count > 0) {
      m_taskQueue.Dequeue().RunSynchronously();
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 原则是合理的,但这种实现实际上并不起作用. (2认同)

Ale*_*x G 7

对我有用的解决方案是将TaskScheduler作为依赖项发送到我想要进行单元测试的代码(例如

MyClass(TaskScheduler asyncScheduler, TaskScheduler guiScheduler)
Run Code Online (Sandbox Code Playgroud)

其中asyncScheduler用于调度在工作线程上运行的任务(阻塞调用),而guiScheduler用于调度应在GUI上运行的任务(非阻塞调用).

在单元测试中,我会注入一个特定的调度程序,即CurrentThreadTaskScheduler实例.CurrentThreadTaskScheduler是一个调度程序实现,它可以立即运行任务,而不是对它们进行排队.

您可以在此处找到Microsoft Samples for Parallel Programming中的实现.

我将粘贴代码以供快速参考:

/// <summary>Provides a task scheduler that runs tasks on the current thread.</summary>
public sealed class CurrentThreadTaskScheduler : TaskScheduler
{
    /// <summary>Runs the provided Task synchronously on the current thread.</summary>
    /// <param name="task">The task to be executed.</param>
    protected override void QueueTask(Task task)
    {
        TryExecuteTask(task);
    }

    /// <summary>Runs the provided Task synchronously on the current thread.</summary>
    /// <param name="task">The task to be executed.</param>
    /// <param name="taskWasPreviouslyQueued">Whether the Task was previously queued to the scheduler.</param>
    /// <returns>True if the Task was successfully executed; otherwise, false.</returns>
    protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
    {
        return TryExecuteTask(task);
    }

    /// <summary>Gets the Tasks currently scheduled to this scheduler.</summary>
    /// <returns>An empty enumerable, as Tasks are never queued, only executed.</returns>
    protected override IEnumerable<Task> GetScheduledTasks()
    {
        return Enumerable.Empty<Task>();
    }

    /// <summary>Gets the maximum degree of parallelism for this scheduler.</summary>
    public override int MaximumConcurrencyLevel { get { return 1; } }
}
Run Code Online (Sandbox Code Playgroud)