通过线程代码创建的线程数的单元测试

布朗达

我的应用程序中有一个线程模块,用于管理并行操作。由于种种原因,它增加了计时和日志记录的各个方面,而且非常复杂,以至于我最近发现我在一个错误中进行了编码,从而在双嵌套线程上启动了一些任务。

即它调用的等效项是:

Task.Run(
    async () => await Task.Run(
        () => DoStuff();
    );
).Wait()

现在,一方面,该代码起作用了……目标代码被运行,并且等待的代码直到目标代码完成后才继续。

另一方面,它使用2个线程而不是1个线程来执行此操作,并且由于我们在线程饥饿方面遇到问题,因此这是一个问题。

我知道如何修复代码,但我想编写一个单元测试以确保A)我已经修复了所有此类错误,并在所有情况下均对其进行了修复。B)没有人会在将来重现此错误。

但是我看不到如何掌握“我创建的所有线程”。CurrentProcess.Threads给了我线程的负载,并且没有明显的方法来识别我所关心的线程。

有什么想法吗?

杰米·汉弗莱斯(Jamie Humphries)

正如单元测试通常涉及一种涉及静态方法的解决方案(Task.Run在这种情况下)一样,您可能需要将某些内容作为依赖项传递给类,以将其包装起来,然后可以在测试中添加行为。

正如@Rich在他的回答中所建议的那样,您可以通过输入来实现TaskScheduler这样,您的测试版本可以在排队任务时维护其数量。

TaskScheduler由于保护级别的缘故,进行测试实际上有点难看,但是在本文的底部,我提供了一个包含现有内容的测试TaskScheduler(例如,您可以使用TaskScheduler.Default)。

不幸的是,您还需要更改通话内容,例如

Task.Run(() => DoSomething);

Task.Factory.StartNew(
    () => DoSomething(),
    CancellationToken.None,
    TaskCreationOptions.DenyChildAttach,
    myTaskScheduler);

基本上是Task.Run在引擎盖下做的,除了引擎盖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第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();
    }
}

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

通过线程创建控件

来自分类Dev

多线程单元测试

来自分类Dev

线程安全单元测试

来自分类Dev

如何对该线程代码进行单元测试

来自分类Dev

MVVM,通过线程和测试获取数据

来自分类Dev

阻塞线程的单元测试方法

来自分类Dev

在单元测试中生成线程

来自分类Dev

加快我可以通过线程抓取的页面数

来自分类Dev

如何在多线程方案中对仅执行一次的代码进行单元测试?

来自分类Dev

通过线程缩放图像-JAVA

来自分类Dev

通过线程名称java查找实例

来自分类Dev

通过线程缩放图像-JAVA

来自分类Dev

通过线程ID查找进程

来自分类Dev

通过线程启动杀死进程

来自分类Dev

创建目录的单元测试代码

来自分类Dev

通过线程句柄获取线程的TIB / TEB(2015)

来自分类Dev

通过线程安全的容器传递非线程安全的对象

来自分类Dev

如何单元测试,一个线程被催生?

来自分类Dev

如何对Java多线程进行单元测试

来自分类Dev

使用接口对后台线程进行单元测试

来自分类Dev

是否可以进行单元测试的多线程?

来自分类Dev

如何通过线程创建表单使应用程序快速启动

来自分类Dev

通过故意破坏代码来验证单元测试

来自分类Dev

Android-通过线程更新UI元素

来自分类Dev

通过线程和SIMD并行化矩阵乘法

来自分类Dev

通过线程中的套接字进行对象输入/输出

来自分类Dev

通过线程实现动态编程算法来固定

来自分类Dev

Android-通过线程更新UI元素

来自分类Dev

单元测试模板代码