如何使用TPL和TaskScheduler编写unit testing
想象一下这样的函数:
private static ConcurrentList list = new ConcurrentList(); public void Add(object x) { Task.Factory.StartNew(() => { list.Add(x); } }
我不在乎什么时候确实将fentry添加到列表中,但是我需要将它添加到最后(显然;))
我没有看到一种方法来正确地unit testing这样的东西而不返回任何回调处理程序或某事。 因此,添加程序不需要的逻辑
你会怎么做?
一种方法是使您的类型可配置,以便它需要一个TaskScheduler
实例。
public MyCollection(TaskScheduler scheduler) { this.taskFactory = new TaskFactory(scheduler); } public void Add(object x) { taskFactory.StartNew(() => { list.Add(x); }); }
现在,在您的unit testing中,您可以创建一个可测试版本的TaskScheduler
。 这是一个可配置的抽象类。 简单地使用schedule函数将项添加到队列中,然后添加一个函数以“现在”手动执行所有队列项。 然后你的unit testing看起来像这样
var scheduler = new TestableScheduler(); var collection = new MyCollection(scehduler); collection.Add(42); scheduler.RunAll(); Assert.IsTrue(collection.Contains(42));
TestableScehduler
示例实现
class TestableScheduler : TaskScheduler { private Queue m_taskQueue = new Queue (); protected override IEnumerable 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(); } } }
对我有用的解决方案是将TaskScheduler作为依赖项发送到我想要进行unit testing的代码(例如
MyClass(TaskScheduler asyncScheduler, TaskScheduler guiScheduler)
其中asyncScheduler用于计划在工作线程上运行的任务(阻塞调用),而guiScheduler用于计划应在GUI上运行的任务(非阻塞调用)。
在unit testing中,我会注入一个特定的调度程序,即CurrentThreadTaskScheduler实例。 CurrentThreadTaskScheduler是一个调度程序实现,它可以立即运行任务,而不是对它们进行排队。
您可以在此处找到Microsoft Samples for Parallel Programming中的实现。
我将粘贴代码以供快速参考:
/// Provides a task scheduler that runs tasks on the current thread. public sealed class CurrentThreadTaskScheduler : TaskScheduler { /// Runs the provided Task synchronously on the current thread. /// The task to be executed. protected override void QueueTask(Task task) { TryExecuteTask(task); } /// Runs the provided Task synchronously on the current thread. /// The task to be executed. /// Whether the Task was previously queued to the scheduler. /// True if the Task was successfully executed; otherwise, false. protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) { return TryExecuteTask(task); } /// Gets the Tasks currently scheduled to this scheduler. /// An empty enumerable, as Tasks are never queued, only executed. protected override IEnumerable GetScheduledTasks() { return Enumerable.Empty (); } /// Gets the maximum degree of parallelism for this scheduler. public override int MaximumConcurrencyLevel { get { return 1; } } }
如何为列表制作公共财产?
public ConcurrentList
或者在DEBUG构建时使其成为公共字段:
#if DEBUG public static ConcurrentList
我和我的同事正在构建一个unit testing框架来解决TPL和Rx测试问题,并且有一个类可以用来替换测试场景中的默认TaskScheduler,这样就不需要修改方法签名了。 。 项目本身尚未发布,但您可以在此处浏览文件:
设置任务调度程序的工作在TplContextAspectAttribute.cs中完成。
对于至少大多数简单的案例,我喜欢对这类事情使用“过期”断言。 例如 :
YourCollection sut = new YourCollection(); object newItem = new object(); sut.Add(newItem); EventualAssert.IsTrue(() => sut.Contains(newItem), TimeSpan.FromSeconds(2));
其中EventualAssert.IsTrue()
看起来像这样:
public static void IsTrue(Func condition, TimeSpan timeout) { if (!SpinWait.SpinUntil(condition, timeout)) { Assert.IsTrue(condition()); } }
我通常也会添加一个带有默认超时的覆盖,我会在大多数测试中使用它,但是ymmv …