How can I unit test a recursive functions in python?

Viewed 1925

I was wondering how can I unit test if a recursive function has been called correctly. For example this function:

def test01(number):
        if(len(number) == 1):
            return 1
        else:
            return 1+test01(number[1:])

It counts recursvely how many digits a number has (assuming the number type is string) So, I want to test if the function test01 has been called recursively. It would be ok if it is implemented just like that, but not if it is implemented as:

def test01(number):
    return len(number)

EDIT: The recursive approach is mandatory for educational purposes, so the UnitTest process will automate programming exercises checking. Is there a way to check if the function was called more than once? If that is possible, I can have 2 tests, one asserting the correct output and one to check if the function was called more than once for the same input.

Thank you in advance for your help

3 Answers

Guessing by the tags I assume you want to use unittest to test for the recursive call. Here is an example for such a check:

from unittest import TestCase
import my_module

class RecursionTest(TestCase):
    def setUp(self):
        self.counter = 0  # counts the number of calls

    def checked_fct(self, fct):  # wrapper function that increases a counter on each call
        def wrapped(*args, **kwargs):
            self.counter += 1
            return fct(*args, **kwargs)

        return wrapped

    def test_recursion(self):
        # replace your function with the checked version
        with mock.patch('my_module.test01',
                        self.checked_fct(my_module.test01)):  # assuming test01 lives in my_module.py
            result = my_module.test01('444')  # call the function
            self.assertEqual(result, 3)  # check for the correct result
            self.assertGreater(self.counter, 1)  # ensure the function has been called more than once

Note: I used import my_module instead of from my_module import test01 so that the first call is also mocked - otherwise the number of calls would be one too low.

Depending on how your setup looks like, you may add further tests manually, or auto-generate the test code for each test, or use parametrization with pytest, or do something else to automate the tests.

Normally a unit test should check at least that your function works and try to test all code paths in it

Your unit test should therefore try to take the main path several times, and then find the exit path, attaining full coverage

You can use the 3rd-party coverage module to see if all your code paths are being taken

pip install coverage

python -m coverage erase       # coverage is additive, so clear out old runs
python -m coverage run -m unittest discover tests/unit_tests
python -m coverage report -m   # report, showing missed lines

Curtis Schlak taught me this strategy recently.

It utilizes Abstract Syntax Trees and the inspect module.

All my best,
Shawn

import unittest
import ast
import inspect
from so import test01


class Test(unittest.TestCase):

    # Check to see if function calls itself recursively
    def test_has_recursive_call(self):

        # Boolean switch
        has_recursive_call = False

        # converts function into a string
        src = inspect.getsource(test01)

        # splits the source code into tokens
        # based on the grammar
        # transformed into an Abstract Syntax Tree
        tree = ast.parse(src)

        # walk tree
        for node in ast.walk(tree):

            # check for function call
            # and if the func called was "test01"
            if (
                type(node) is ast.Call
                and node.func.id == "test01"
            ):

                # flip Boolean switch to true
                has_recursive_call = True

        # assert: has_recursive_call should be true
        self.assertTrue(
            has_recursive_call,
            msg="The function does not "
                "make a recursive call",
        )

        print("\nThe function makes a recursive call")


if __name__ == "__main__":

    unittest.main()

Related