Unit testing 如何编写递归方法的模拟测试

Unit testing 如何编写递归方法的模拟测试,unit-testing,mocking,rhino-mocks,moq,Unit Testing,Mocking,Rhino Mocks,Moq,如果我有一个在特定条件下调用自身的方法,是否可以编写一个测试来验证该行为?我很想看一个例子,我不关心模拟框架或语言。我在C#中使用Rhinomock,所以我很好奇这是否是框架中缺少的功能,或者我误解了一些基本的东西,或者这只是不可能的事情。在我所知道的任何模拟框架中,都没有任何东西可以监视堆栈深度/函数调用(递归)的数量。但是,正确模拟的前置条件提供正确输出的单元测试应该与模拟非递归函数相同 无限递归会导致堆栈溢出,您必须单独调试,但单元测试和模拟从一开始就没有摆脱这种需要。假设您想从完整路径获

如果我有一个在特定条件下调用自身的方法,是否可以编写一个测试来验证该行为?我很想看一个例子,我不关心模拟框架或语言。我在C#中使用Rhinomock,所以我很好奇这是否是框架中缺少的功能,或者我误解了一些基本的东西,或者这只是不可能的事情。

在我所知道的任何模拟框架中,都没有任何东西可以监视堆栈深度/函数调用(递归)的数量。但是,正确模拟的前置条件提供正确输出的单元测试应该与模拟非递归函数相同


无限递归会导致堆栈溢出,您必须单独调试,但单元测试和模拟从一开始就没有摆脱这种需要。

假设您想从完整路径获取文件名,例如:

c:/windows/awesome/lol.cs -> lol.cs
c:/windows/awesome/yeah/lol.cs -> lol.cs
lol.cs -> lol.cs
你有:

public getFilename(String original) {
  var stripped = original;
  while(hasSlashes(stripped)) {
    stripped = stripped.substringAfterFirstSlash(); 
  }
  return stripped;
}
你想写:

public getFilename(String original) {
  if(hasSlashes(original)) {
    return getFilename(original.substringAfterFirstSlash()); 
  }
  return original;
}
这里的递归是一个实现细节,不应该进行测试。您确实希望能够在这两个实现之间切换,并验证它们是否产生相同的结果:对于上述三个示例,两者都产生lol.cs

也就是说,因为您是按名称递归的,而不是说thisMethod.reach()等,所以在Ruby中,您可以将原始方法别名为新名称,用旧名称重新定义该方法,调用新名称,并检查是否以新定义的方法结束

def blah
  puts "in blah"
  blah
end

alias blah2 blah

def blah
  puts "new blah"
end

blah2
在特定条件下调用自身的方法,是否可以编写测试来验证行为

对。但是,如果需要测试递归,最好将入口点分为递归和递归步骤,以便进行测试

无论如何,下面是一个例子,如果你做不到,如何测试它。你真的不需要任何嘲笑:

// Class under test
public class Factorial
{
    public virtual int Calculate(int number)
    {
        if (number < 2)
            return 1
        return Calculate(number-1) * number;
    }
}

// The helper class to test the recursion
public class FactorialTester : Factorial
{
    public int NumberOfCalls { get; set; }

    public override int Calculate(int number)
    {
        NumberOfCalls++;
        return base.Calculate(number)
    }
}    

// Testing
[Test]
public void IsCalledAtLeastOnce()
{
    var tester = new FactorialTester();
    tester.Calculate(1);
    Assert.GreaterOrEqual(1, tester.NumberOfCalls  );
}
[Test]
public void IsCalled3TimesForNumber3()
{
    var tester = new FactorialTester();
    tester.Calculate(3);
    Assert.AreEqual(3, tester.NumberOfCalls  );
}
//测试中的类
公共类阶乘
{
公共虚拟整数计算(整数)
{
如果(数字<2)
返回1
返回计算(编号-1)*编号;
}
}
//用于测试递归的helper类
公共类阶乘酯:阶乘
{
公共int NumberOfCalls{get;set;}
公共覆盖整数计算(整数)
{
NumberOfCalls++;
返回基数。计算(数字)
}
}    
//测试
[测试]
公共无效被称为leasttonce()
{
var测试器=新的阶乘酯();
测试仪。计算(1);
Assert.GreaterOrEqual(1,tester.NumberOfCalls);
}
[测试]
public void为number3()调用3次
{
var测试器=新的阶乘酯();
计算(3);
Assert.AreEqual(3,tester.NumberOfCalls);
}

您误解了模拟对象的用途。mock(在Mockist的意义上)用于测试与被测试系统的依赖关系的行为交互

例如,你可能有这样的想法:

interface IMailOrder
{
   void OrderExplosives();
}

class Coyote
{
   public Coyote(IMailOrder mailOrder) {}

   public void CatchDinner() {}
}
土狼依赖伊玛洛德。在生产代码中,Coyote的实例将被传递给Acme的实例,Acme实现了IMailOrder。(这可以通过手动依赖项注入或通过DI框架完成。)

您需要测试方法CatchDinner并验证它是否调用OrderExplorides。为此,您:

  • 创建一个实现IMailOrder的mock对象,并通过将mock对象传递给其构造函数来创建Coyote(测试中的系统)的实例。(安排)
  • 请给我打电话吃晚饭。(法案)
  • 要求模拟对象验证是否满足给定的期望(已调用)。(断言)
  • 设置模拟对象的期望值时,可能取决于模拟(隔离)框架


    如果要测试的类或方法没有外部依赖项,则不需要(或不希望)为该测试集使用模拟对象。方法是否是递归的并不重要

    通常需要测试边界条件,因此可以测试不应该是递归的调用、使用单个递归调用的调用和深度递归调用。(不过,miaubiz有一个很好的观点,那就是递归是一个实现细节。)

    EDIT:在最后一段中,我所说的“调用”是指带有参数或对象状态的调用,这些参数或对象状态将触发给定的递归深度。我也推荐阅读

    编辑2:示例测试代码,使用:

    var mockMailOrder=new Mock();
    var wily=新的郊狼(mockMailOrder.Object);
    诡计多端的;机智的;
    验证(x=>x.orderExplores());
    
    以下是我的“农民”方法(在Python中,经过测试,请参阅注释了解基本原理)

    请注意,这里的实现细节“公开”是毫无疑问的,因为您正在测试的是底层架构,而“顶级”代码恰好使用了该架构。所以,测试它是合法的,并且表现良好(我也希望,这是您的想法)

    代码(主要思想是从一个单一但“不稳定”的递归函数到一对等效的递归依赖(因此是可测试的)函数):

    测试:

    import unittest
    
    class TestFactorial(unittest.TestCase):
    
        def test_impl(self):
            """Test the 'factorial_impl' function,
            'wiring' it to a specially constructed 'fct'"""
    
            def fct(n):
                """To be 'injected'
                as a 'factorial_impl''s 'fct' parameter"""
                # Use a simple number, which will 'show' itself
                # in the 'factorial_impl' return value.
                return 100
    
            # Here we must get '1'.
            self.assertEqual(factorial_impl(1, fct), 1)
            # Here we must get 'n*100', note the ease of testing:)
            self.assertEqual(factorial_impl(2, fct), 2*100)
            self.assertEqual(factorial_impl(3, fct), 3*100)
    
        def test(self):
            """Test the 'factorial' function"""
            self.assertEqual(factorial(1), 1)
            self.assertEqual(factorial(2), 2)
            self.assertEqual(factorial(3), 6)
    
    输出:

    Finding files...
    ['...py'] ... done
    Importing test modules ... done.
    
    Test the 'factorial' function ... ok
    Test the 'factorial_impl' function, ... ok
    
    ----------------------------------------------------------------------
    Ran 2 tests in 0.000s
    
    OK
    

    你是说在这种情况下,一个验证方法状态的单元测试已经足够好了吗?对不起,我不完全理解你的问题。在我的文件路径示例中,验证方法输出的单元测试就足够了,甚至比验证递归的单元测试更好。但是,我不知道您的具体情况,所以可能会有所不同。@jayrdub-一般来说,状态验证正是您希望单元测试执行的。检查方法的返回值和/或测试对象的公共属性。其他一切都是实现细节,在重构过程中可能会发生变化。我不清楚。你到底想测试什么?如果您正在测试的类或方法没有exte,那么该方法“在特定条件下”(即“调用堆栈”将遵循“在特定条件下”的特定路径)调用自己还是其他什么
    import unittest
    
    class TestFactorial(unittest.TestCase):
    
        def test_impl(self):
            """Test the 'factorial_impl' function,
            'wiring' it to a specially constructed 'fct'"""
    
            def fct(n):
                """To be 'injected'
                as a 'factorial_impl''s 'fct' parameter"""
                # Use a simple number, which will 'show' itself
                # in the 'factorial_impl' return value.
                return 100
    
            # Here we must get '1'.
            self.assertEqual(factorial_impl(1, fct), 1)
            # Here we must get 'n*100', note the ease of testing:)
            self.assertEqual(factorial_impl(2, fct), 2*100)
            self.assertEqual(factorial_impl(3, fct), 3*100)
    
        def test(self):
            """Test the 'factorial' function"""
            self.assertEqual(factorial(1), 1)
            self.assertEqual(factorial(2), 2)
            self.assertEqual(factorial(3), 6)
    
    Finding files...
    ['...py'] ... done
    Importing test modules ... done.
    
    Test the 'factorial' function ... ok
    Test the 'factorial_impl' function, ... ok
    
    ----------------------------------------------------------------------
    Ran 2 tests in 0.000s
    
    OK