由于通常是单元测试涉及静态方法(本例中为Task.Run
)的解决方案,因此您可能需要将某些内容作为依赖项传递给您的类,将其包装起来,然后可以添加行为在测试中。
正如@Rich在他的回答中所建议的,你可以通过传递TaskScheduler
来实现。然后,您的测试版本可以在任务排入队列时保持其计数。
制作测试TaskScheduler
实际上是因为保护级别有点难看,但在这个岗位的底部我已经包含了一个包装现有TaskScheduler
(例如,你可以使用TaskScheduler.Default
)。
不幸的是,你还需要像
Task.Run(() => DoSomething);
改变你的来电像
Task.Factory.StartNew(
() => DoSomething(),
CancellationToken.None,
TaskCreationOptions.DenyChildAttach,
myTaskScheduler);
这是basically what Task.Run
does under the hood,除了与TaskScheduler.Default
。你当然可以将它包装在某处的辅助方法中。
或者,如果你不娇气有关在您的测试代码中的一些风险较高的反射,你可以劫持TaskScheduler.Default
属性,因此,您仍然可以只使用Task.Run
:
var defaultSchedulerField = typeof(TaskScheduler).GetField("s_defaultTaskScheduler", BindingFlags.Static | BindingFlags.NonPublic);
var scheduler = new TestTaskScheduler(TaskScheduler.Default);
defaultSchedulerField.SetValue(null, scheduler);
(私人字段名是从TaskScheduler.cs line 285。 )
因此,例如,本次测试将通过使用下面我TestTaskScheduler
和反射招:
[Test]
public void Can_count_tasks()
{
// Given
var originalScheduler = TaskScheduler.Default;
var defaultSchedulerField = typeof(TaskScheduler).GetField("s_defaultTaskScheduler", BindingFlags.Static | BindingFlags.NonPublic);
var testScheduler = new TestTaskScheduler(originalScheduler);
defaultSchedulerField.SetValue(null, testScheduler);
// When
Task.Run(() => {});
Task.Run(() => {});
Task.Run(() => {});
// Then
testScheduler.TaskCount.Should().Be(3);
// Clean up
defaultSchedulerField.SetValue(null, originalScheduler);
}
下面是测试任务调度程序:
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;
public class TestTaskScheduler : TaskScheduler
{
private static readonly MethodInfo queueTask = GetProtectedMethodInfo("QueueTask");
private static readonly MethodInfo tryExecuteTaskInline = GetProtectedMethodInfo("TryExecuteTaskInline");
private static readonly MethodInfo getScheduledTasks = GetProtectedMethodInfo("GetScheduledTasks");
private readonly TaskScheduler taskScheduler;
public TestTaskScheduler(TaskScheduler taskScheduler)
{
this.taskScheduler = taskScheduler;
}
public int TaskCount { get; private set; }
protected override void QueueTask(Task task)
{
TaskCount++;
CallProtectedMethod(queueTask, task);
}
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
return (bool)CallProtectedMethod(tryExecuteTaskInline, task, taskWasPreviouslyQueued);
}
protected override IEnumerable<Task> GetScheduledTasks()
{
return (IEnumerable<Task>)CallProtectedMethod(getScheduledTasks);
}
private object CallProtectedMethod(MethodInfo methodInfo, params object[] args)
{
return methodInfo.Invoke(taskScheduler, args);
}
private static MethodInfo GetProtectedMethodInfo(string methodName)
{
return typeof(TaskScheduler).GetMethod(methodName, BindingFlags.Instance | BindingFlags.NonPublic);
}
}
或收拾使用RelflectionMagic由@hgcummings在评论中建议:
var scheduler = new TestTaskScheduler(TaskScheduler.Default);
typeof(TaskScheduler).AsDynamicType().s_defaultTaskScheduler = scheduler;
using System.Collections.Generic;
using System.Threading.Tasks;
using ReflectionMagic;
public class TestTaskScheduler : TaskScheduler
{
private readonly dynamic taskScheduler;
public TestTaskScheduler(TaskScheduler taskScheduler)
{
this.taskScheduler = taskScheduler.AsDynamic();
}
public int TaskCount { get; private set; }
protected override void QueueTask(Task task)
{
TaskCount++;
taskScheduler.QueueTask(task);
}
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
return taskScheduler.TryExecuteTaskInline(task, taskWasPreviouslyQueued);
}
protected override IEnumerable<Task> GetScheduledTasks()
{
return taskScheduler.GetScheduledTasks();
}
}
多数民众赞成...惊人?可怕的?我真的不明白为什么“使用这个作为默认调度程序”不是你可以JustDo的东西,但很高兴知道如何去做。 – Brondahl
不错(或者可能是邪恶的)!你可以使用ReflectionMagic的AsDynamic来简化TestTaskRunner(虽然可能会有性能问题)。请参阅https://blogs.msdn.microsoft.com/davidebb/2010/01/18/use-c-4-0-dynamic-to-drastically-simplify-your-private-reflection-code/ – hgcummings
很好。我会添加一些清理代码,以便在测试结束时再次将TaskScheduler.Default返回,否则您可能会对其他稍后的测试产生不必要的干扰。 – Rich