3

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?

2
  • Are you using unittest? Commented Oct 20, 2017 at 19:03
  • Unless bool_function has side effects why bother. Can't you just test with n that you know hits a True? Commented Oct 20, 2017 at 19:54

5 Answers 5

5

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
Sign up to request clarification or add additional context in comments.

Comments

1

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.

Comments

0

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

3 Comments

I strongly recommend not to pass any debug arguments. If function cannot be tested - it means that design is bad, adding one more workaround does not guarantee that function itself works.
@TarasMatsyk perhaps this is the result of an over-simplified example, but I believe the greater simplicity and readability outweighs the modularity in this case. Particularly if the predicate will otherwise only ever be a single function, this approach both simplifies the reading of the code as well as the execution.
agree from this perspective
0

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)

Comments

0

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)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.