如何在Python中单元测试递归函数?

3

我想知道如何对递归函数进行单元测试,以确保其调用正确。例如,以下函数:

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

它递归地计算一个数字有多少位数(假设数字类型为字符串)。 因此,我想测试函数test01是否已经被递归调用。如果仅按照这种方式实现,那么就可以了,但如果按照以下方式实现,则不行:

def test01(number):
    return len(number)

编辑: 为了教育目的,递归方法是必要的,因此UnitTest过程将自动化编程练习检查。有没有方法可以检查函数是否被调用超过一次?如果可能的话,我可以进行两个测试,一个断言正确的输出,另一个检查相同输入时函数是否被调用多次。
非常感谢您的帮助。

为什么上述函数在测试时应该与迭代实现有所不同? - Balaji Ambresh
1
那将是测试实现,而不是行为。如果以不同的方式重构以给出正确的答案,测试应该失败吗? - jonrsharpe
你的欲望是可以理解的(我年轻时也有这种欲望),但是我鼓励你忽略它:测试你的函数行为(它是否给出正确的答案),而不是它的实现细节。如果这个函数通过打电话给先知神奇地得到了正确的答案,那你真的在乎吗? - FMc
1
单元测试是您编写的测试,用于验证函数在您可以想到的每种情况下是否正常工作。您控制它的调用方式,因此无法发生错误调用。从您提出的问题来看,更像是希望进行运行时检查以验证输入。 - Kenny Ostrom
或许你正在询问如何向用户展示一个接口,但在内部递归调用时使用另一个接口。这很容易——只需给递归函数另一个名称,通常以下划线开头,并从公开导出的函数中调用它即可。 - Kenny Ostrom
大家好,感谢您们的回复和时间。抱歉我没有提到,主要目标是自动化编程练习检查。这次,我们应该检查学生是否使用了递归方法。 - Alan Morante
3个回答

2

根据标签猜测,我认为您想使用unittest来测试递归调用。以下是一个检查的示例:

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

注意: 我使用了 import my_module 而不是 from my_module import test01,这样第一次调用也被模拟了 - 否则调用的次数会少一个。

根据你的设置,你可以手动添加更多的测试,或为每个测试自动生成测试代码,或使用pytest的参数化,或做其他事情来自动化测试。


每次我看到MrBean Bremen关于Python测试的回答,我都会学到新东西。这里的补丁甚至没有使用side_effect。我不知道你可以在那个点上只是把一个函数放进去。现在我看到了它,它就很有意义...但那是在我看到它之后! - mike rodent

0

最近Curtis Schlak教了我这个策略。

它利用抽象语法树检查模块

祝一切顺利,
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()


0

通常,单元测试应该至少检查您的函数是否正常工作,并尝试测试其中的所有代码路径

因此,您的单元测试应该尝试多次执行主要路径,然后找到退出路径,实现完全覆盖

您可以使用第三方coverage模块来查看是否正在执行所有代码路径

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接