由於通常是單元測試涉及靜態方法(本例中爲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