在Python 3.6中,是否有可能模拟内置的len()函数?

17

Python 3.6中是否可以模拟内置的len()函数?

我有一个类,定义了一个简单的方法,依赖于len()函数,如下所示:

class MyLenFunc(object):
    def is_longer_than_three_characters(self, some_string):
        return len(some_string) > 3

我尝试为这个方法编写单元测试,但是当我试图模拟 len() 函数时,会导致出现错误。

以下是我的部分代码:

import unittest
from unittest.mock import patch
import my_len_func


class TestMyLenFunc(unittest.TestCase):

    @patch('builtins.len')
    def test_len_function_is_called(self, mock_len):
        # Arrange
        test_string = 'four'

        # Act
        test_object = my_len_func.MyLenFunc()
        test_object.is_longer_than_three_characters(test_string)

        # Assert
        self.assertEqual(1, mock_len.call_count)


if __name__ == '__main__':
    unittest.main()

我在这里找到了另一个关于SO的问题/答案(链接),它表明无法模拟内置函数,因为它们是不可变的。但是,我在这里这里发现了更多的网站,它们提出了相反的观点。我尝试使用上述单元测试类,直接从后者的网站中获取(是的,我尝试过其中提到的其他技术,但最终都以相同的错误结束)。

我遇到的错误太长了,无法完整贴出,因此我将剪辑掉其中重复的部分(您将看到它从错误消息的最终部分递归)。错误文本如下:

ERROR: test_len_function_is_called (__main__.TestMyLenFunc)
---------------------------------------------------------------------- 
Traceback (most recent call last):
    File "C:\Python36\Lib\unittest\mock.py", line 1179, in patched
        return func(*args, **keywargs)
    File "C:/Python/scratchpad/my_len_func_tests.py", line 16, in test_len_function_is_called
        test_object.is_longer_than_three_characters(test_string)
    File "C:\Python\scratchpad\my_len_func.py", line 3, in is_longer_than_three_characters
        return len(some_string) > 3
    File "C:\Python36\Lib\unittest\mock.py", line 939, in __call__
        return _mock_self._mock_call(*args, **kwargs)
    File "C:\Python36\Lib\unittest\mock.py", line 949, in _mock_call
        _call = _Call((args, kwargs), two=True)
    File "C:\Python36\Lib\unittest\mock.py", line 1972, in __new__
        _len = len(value)
    ...
    (Repeat errors from lines 939, 949, and 1972 another 95 times!)
    ...
    File "C:\Python36\Lib\unittest\mock.py", line 939, in __call__
        return _mock_self._mock_call(*args, **kwargs)
    File "C:\Python36\Lib\unittest\mock.py", line 944, in _mock_call
        self.called = True RecursionError: maximum recursion depth exceeded while calling a Python object

----------------------------------------------------------------------
Ran 1 test in 0.004s

FAILED (errors=1)

非常感谢您的预先帮助。


1
你能模拟相应的 __len__() 方法吗? - Eric Duminil
你找到的答案涉及内置类型的方法len()不是这样的方法。你确实可以只替换builtins上的引用,以使用不同的实现来替换它。但对于str.count等方法,你不能这样做。 - Martijn Pieters
@eric-duminil 谢谢您的建议。我确实尝试过,但是出现了错误 AttributeError: <module 'builtins' (built-in)> does not have the attribute '__len__' - DatHydroGuy
@martijn-pieters 哎呀!你说得对!我试图模拟错误的东西,不是吗?非常感谢你,还有你下面的回答。 - DatHydroGuy
@DatHydroGuy:Eric 的意思是让你传入一个带有模拟的 __len__ 的对象(因为对于自定义 Python 类的实例,len(ob) 调用的是 type(ob).__len__(ob))。然而,这会使事情变得不必要地复杂,因为现在你需要在一个模拟对象中模拟所有其他 str 提供的操作和你的代码所需的操作,以替换你传入的字符串。 - Martijn Pieters
啊,我明白了。抱歉我误解了。这是一个有趣的想法,但正如你所说,它增加了复杂性,尤其是如果我需要从str中获得额外的功能。 - DatHydroGuy
1个回答

24

不要打补丁builtins.len; 现在mock库中的代码会出问题,因为它需要函数以正常方式运行!请打补丁模块测试下的全局变量:

@patch('my_len_func.len')

这将向您的模块中添加全局 len,并且在MyLenFunc().is_longer_than_three_characters()方法中的len(some_string)将找到该全局变量而不是内置函数。

然而,我必须说测试是否调用了len()感觉就像是在测试错误的东西。你想要检查的是该方法针对给定输入是否产生了正确的结果,基本操作如len()只是朝着这个目标的小构建块,通常不会被测试到这个程度。


3
非常感谢。这种方法非常有效。我完全同意测试len()是否被调用是错误的。这只是我正在处理的实际问题的简化示例,正好说明了我遇到的问题。 - DatHydroGuy
不错的观点,但是有人可能想要获取len的返回值以在测试的另一步骤中使用。 - user3841581

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