【问题标题】:Test Recursive Python Function测试递归 Python 函数
【发布时间】:2020-01-02 21:38:51
【问题描述】:

我有一个要测试的递归函数,但是在测试期间我很难限制递归调用。例如,下面是一个递归函数的简单示例,它调用 bool_function(n) 来检查它是否应该中断递归循环。

def factorial(n):
  if bool_function(n):
      return 1
  else:
      return n * factorial(n-1)

测试或模拟 bool_function(n) 的最佳方法是什么,以便第一次迭代为真,之后的任何调用为假?

【问题讨论】:

  • 你在使用unittest吗?
  • 除非bool_function 有副作用,为什么还要麻烦。你不能用你知道的n 测试True 吗?

标签: python testing recursion mocking


【解决方案1】:

你总是可以实现一个类来封装状态并给你更多的灵活性,这是一个草图:

>>> class MockBoolCheck:
...     def __init__(self, fail_after=0):
...         self.count = 0
...         self.fail_after = fail_after
...     def __call__(self, n):
...         called = self.count
...         self.count += 1
...         return called <= self.fail_after
...
>>> bool_function = MockBoolCheck()
>>> bool_function(42)
True
>>> bool_function(42)
False
>>> bool_function(42)
False
>>> bool_function(42)
False
>>> bool_function(42)
False

【讨论】:

    【解决方案2】:

    如果除了其他建议的解决方案之外,您真的想模拟它,并且想自己做(没有模拟库)只需替换模拟函数。

    # Your code (or module):
    
    def bool_function(n):
        print('REAL bool-function {}'.format(n))
        return n <= 0
    
    def factorial(n):
        print('FACT {}'.format(n))
        if bool_function(n):
            return 1
        else:
            return n * factorial(n-1)
    
    # Mocking code (or module):
    
    def mock_function(n):
        print('MOCK bool-function {}'.format(n))
        global bool_function
        bool_function = bool_func_orig  # restore on the first use
        return False
    bool_func_orig = bool_function
    bool_function = mock_function  # mock it
    
    # Go run it!
    factorial(10)
    

    如果它们是两个独立的模块,则不要使用 global bool_functionbool_function=...,只需使用 somemodule.bool_function=...

    如果你想使用模拟库,那么这取决于你使用哪个库。如果那是unittest.mock,那么您应该使用side_effect=...wraps=...(参见manual)。相同的方法:模拟它,并在第一次使用时从副作用内部取消模拟它。

    【讨论】:

      【解决方案3】:

      除非我希望经常使用调试代码,否则我通常会尽量不要留下调试代码,但您可以只包含一个默认参数以进行调试以强制执行遵循特定路径。

      def factorial(n, debug=False):
        if bool_function(n) or debug:
            return 1
        else:
            return n * factorial(n-1)
      

      这自然意味着你也在外部测试bool_function()

      【讨论】:

      • 我强烈建议不要传递任何debug 参数。如果无法测试功能 - 这意味着设计很糟糕,再添加一种解决方法并不能保证功能本身可以正常工作。
      • @TarasMatsyk 也许这是一个过度简化的例子的结果,但我相信在这种情况下,更大的简单性和可读性胜过模块化。特别是如果谓词只能是一个函数,这种方法既可以简化代码的读取,也可以简化执行。
      • 从这个角度同意
      【解决方案4】:

      只需将函数作为参数传递。如果 function 是 None 如果需要,您可以应用一些默认行为。

      这是大多数语言中queries to iterables(例如 Django 查询或 Peewee 查询)中使用的常用方法。

      返回布尔值的函数通常称为predicate

      def factorial(n, predicate=None):
        if not predicate:
           predicate = lambda x: x > 2
      
        if predicate(n):
            return 1
        else:
            return n * factorial(n-1)
      

      【讨论】:

        【解决方案5】:

        对于 python > 3.6

        import mock   
        class RecursividadeTest(unittest.TestCase):
            def test_recursive(self):
                with mock.patch('path.factorial') as mock_fact:
                    factorial(3)
                    self.assertTrue(mock_fact.called)
                    self.assertGreaterEqual(mock_fact.call_count, 2)
        
            def test_recursive_2(self):
            with mock.patch('incolumepy.sequences.fibonacci.fibonacci') as mock_fib:
                for i in range(1, 5, -1):
                    expected = i - 1
                    fibonacci(i)
                    self.assertTrue(mock_fib.called)
                    self.assertEqual(mock_fib.call_count, expected)
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2017-06-14
          • 1970-01-01
          • 2020-05-25
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-08-29
          相关资源
          最近更新 更多