sokeefe
sokeefe

Reputation: 637

Test Recursive Python Function

I have a recursive function that I'm looking to test, however I'm having difficulty limiting the recursive call during testing. For example, below is a simple example of a recursive function that calls a bool_function(n) to check if it should break the recursive loop.

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

What would be the best way to test or mock bool_function(n) so that it is true for the first iteration and false for any call after?

Upvotes: 3

Views: 5134

Answers (5)

britodfbr
britodfbr

Reputation: 2011

For 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)

Upvotes: 0

Sergey Vasilyev
Sergey Vasilyev

Reputation: 4189

If, beside other suggested solutions, you really want to mock it, and want to do it yourself (without the mocking libraries) by just replacing the mocked function.

# 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)

If these are two separate modules, then instead of global bool_function & bool_function=... just use the somemodule.bool_function=....

If you want to use the mocking library, then it depends on which library you use. If that is unittest.mock, then you should play with side_effect=... & wraps=... (see the manual). The same approach: mock it, and un-mock it from inside the side effect on the first use.

Upvotes: 1

juanpa.arrivillaga
juanpa.arrivillaga

Reputation: 96287

You could always implement a class to encapsulate the state and give you more flexibility, here's a sketch:

>>> 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

Upvotes: 5

taras
taras

Reputation: 3705

Just pass the function as an argument. If function is None you can apply some default behavior if that is desired.

This is a common approach used in queries to iterables (e.g. Django queries or Peewee queries) in most of languages.

A function that returns boolean is usually called a 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)

Upvotes: 0

Aaron
Aaron

Reputation: 11075

I generally try not to leave debug code around unless I expect to use it regularly, but you could just include a default argument for the sake of debugging to force the execution to follow a particular path.

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

This naturally implies that you're also externally testing bool_function()

Upvotes: 0

Related Questions