C# 如何对递归方法进行单元测试?

C# 如何对递归方法进行单元测试?,c#,unit-testing,recursion,C#,Unit Testing,Recursion,我有三个类似这样的函数: private Node GetNode(Node parentNode) { var node = new node(); switch (parentNode.NodeType) { case NodeType.Multiple: node = GetMultipleNode(parentNode) case NodeType.Repeating: node = GetRepeatingNode(par

我有三个类似这样的函数:

private Node GetNode(Node parentNode)
{
    var node = new node();

    switch (parentNode.NodeType)
    {
       case NodeType.Multiple:    node = GetMultipleNode(parentNode)
       case NodeType.Repeating:   node = GetRepeatingNode(parentNode)
    }

    return node;
}

private Node GetMultipleNode(Node parentNode)
{
    foreach (var child in parentNode.Children)
        return GetNode(child);
}

private Node GetRepeatingNode(Node parentNode)
{
    for (int i=0; i < parentNode.Count; i++)
         return GetNode(new Node(i));  // Assume meaningful constructor for Node
}
private节点GetNode(节点parentNode)
{
var node=新节点();
开关(parentNode.NodeType)
{
case NodeType.Multiple:node=GetMultipleNode(parentNode)
案例NodeType.Repeating:node=GetRepeatingNode(parentNode)
}
返回节点;
}
私有节点GetMultipleNode(节点父节点)
{
foreach(parentNode.Children中的变量child)
返回GetNode(子节点);
}
私有节点GetRepeatingNode(节点父节点)
{
for(int i=0;i

考虑到这三种方法是相互递归的,那么如何独立地对它们进行单元测试呢?

通常,您不需要单独测试每种方法,您只需测试顶级方法是否正确

然而,如果出于某种原因,您希望单独测试每个方法,那么您可以使用依赖项注入,就像测试任何具有依赖项的方法一样。这里唯一的区别是依赖关系是对象本身。下面是一些示例代码来演示此想法:

class NodeGetter : INodeGetter
{
    public Node GetNode(Node parentNode)
    {
        return GetNode(parentNode, this);
    } 

    public Node GetNode(Node parentNode, INodeGetter nodeGetter)
    {
        switch (parentNode.NodeType)
        {
           case NodeType.Multiple:
               return nodeGetter.GetMultipleNode(parentNode, nodeGetter);
           case NodeType.Repeating:
               return nodeGetter.GetRepeatingNode(parentNode, nodeGetter);
           default:
               throw new NotSupportedException(
                   "Node type not supported: " + parentNode.NodeType);
        }
    }

    public Node GetMultipleNode(Node parentNode, INodeGetter nodeGetter)
    {
        foreach (Node child in parentNode.Children)
        {
            return nodeGetter.GetNode(child);
        }
    }

    public Node GetRepeatingNode(Node parentNode, INodeGetter nodeGetter)
    {
        for (int i = 0; i < parentNode.Count; i++)
        {
            // Assume meaningful constructor for Node
            return nodeGetter.GetNode(new Node(i));
        }
    }
}
class节点设置器:INodeGetter
{
公共节点GetNode(节点父节点)
{
返回GetNode(parentNode,this);
} 
公共节点GetNode(节点parentNode、INodeGetter节点设置器)
{
开关(parentNode.NodeType)
{
案例节点类型。多个:
返回nodeGetter.GetMultipleNode(父节点,nodeGetter);
案例节点类型。重复:
返回nodeGetter.GetRepeatingNode(parentNode,nodegeter);
违约:
抛出新的NotSupportedException(
不支持节点类型:“+parentNode.NodeType”);
}
}
公共节点GetMultipleNode(节点父节点、INodeGetter节点设置器)
{
foreach(parentNode.Children中的节点子节点)
{
返回nodeGetter.GetNode(子节点);
}
}
公共节点GetRepeatingNode(节点parentNode、INodeGetter节点设置器)
{
for(int i=0;i
测试nodeGeter参数时,通过模拟


我还将您的方法从private改为public,因为最好只测试类的公共接口。

好吧,您不能“独立”地对它们进行单元测试,因为它们显然相互依赖,但原则上您当然可以为GetNode、GetMultipleNode和GetRepeatingNode编写单独的测试,假设可以从使用它们的代码中调用其中的每一个。当然,GetRepeatingNode调用GetNode,等等,但这和调用一些完全外部的函数没有什么不同


顺便说一下,您可能会考虑重构设计,而不是使用NODYPE枚举来使用多态性。只是一个想法:)

如果确实需要,您可以使用接口包装每个方法,并为这些方法创建模拟(例如使用Moq库),或者按照Mark Byers的建议将方法作为参数传递给彼此。但这似乎是一个过于复杂的解决方案


正如我所看到的,您的代码中的方法是私有的,您确定为这样低级的内部实现细节编写单元测试是个好主意吗?我认为这三种方法应该作为一个单元进行测试。这个逻辑被分成三种不同的方法,只是为了提高代码的可读性,而这不是某种公共API,所以您真的需要独立测试它们吗

@Robert Harvey:你可以使用模拟对象。谢谢你重构了我公认的精心设计的示例。通常我只在
案例中返回
语句,而不是添加
break
语句。@Robert Harvey:好主意。更新日期:)我对代码进行了所有更改,并再次运行了我的原始单元测试,使用原始测试对象作为节点设置器。所有现有的测试仍然通过。好极了!现在,我可以使用模拟对象添加一些新的、更集中的测试。谢谢每种方法的实际实现都非常复杂,因此它们需要自己的单元测试
GetNode
是唯一的公共方法,因为这是我希望外部调用方调用的唯一方法。这是否类似于对树中所有节点进行迭代,并在此迭代过程中进行一些复杂计算?例如,您可以使用访问者模式将在数据结构节点上导航的代码与执行某些业务逻辑的代码分开。这里没有业务逻辑,但有四种不同的节点类型,所有这些类型都需要不同的遍历方法。您可以创建从基本
node
类继承的四个类,并为每种类型重写虚拟
GetInnerNode
方法。这似乎是学校手册中关于多态性的经典问题。试想一下,大多数带有
switch(blahBlahType)
语句的代码都可以使用OOP的多态功能重写;在实际的程序中,有一些感知代码分析数据节点并确定应用哪种遍历;实际的代码有点复杂,我认为将其多态化只会混淆每个方法的意图。