无法修补魔法属性

Cannot patch a magic attribute

我希望能够修补魔法属性。

我想测试的主文件是 FileUtil.py,包含 FileUtil 的 class 定义:

class FileUtil:
    @logit(showArgs=True, showRetVal=True)
    def exec_file_path(self) -> str:
        """
        Get the full dir and file path we're executing from using __file__, or if not available (probably because you're using a Jupyter notebook)
        use os.path.abspath.
        """
        try:
            logger.debug('exec_file_path is about to attempt accessing __file__.')
            return __file__
        except NameError as err:
            logger.debug('Exception message: {msg}'.format(msg=err))
            return abspath('')

我在 mockTest.py 中也有一个本地测试,其中包含一个 class AltFileUtil。为了区分调用的是哪个,fUtil实例化了AltFileUtil,gUtil实例化了FileUtil。

被测方法使用__file__,除此之外,使用absdir

from os.path import abspath
from unittest.mock import MagicMock, PropertyMock, patch

class AltFileUtil:
    def exec_file_path(self) -> str:
        """
        Get the full dir and file path we're executing from using __file__, or if not available (probably because you're using a Jupyter notebook)
        use os.path.abspath.
        """
        try:
            return __file__
        except NameError as err:
            return abspath('')

fUtil = AltFileUtil()
print (f'Part 1. function returns: {fUtil.exec_file_path()}')

patches = ['__main__.AltFileUtil', 'mockTest.AltFileUtil', ]
for p in patches:
    print (f'Using patch of {p}.')
    with patch(p) as mockFUtil:
        type(mockFUtil.return_value).__file__ = PropertyMock(return_value='mockfilename')
        x = mockFUtil.exec_file_path()
        try:
            print (f'Success! Got {x.__file__}.')
        except (ValueError, AttributeError, TypeError) as e:
            print(f'Got a {type(e)} error: {e}')

from FileUtil import FileUtil
gUtil = FileUtil()
print (f'Part 2. Using function from FileUtil.py, which returns: {gUtil.exec_file_path()}')

patches = ['FileUtil.FileUtil',  ]
for p in patches:
    print (f'Using patch of {p}.')
    with patch(p) as mockFUtil:
        type(mockFUtil.return_value).__file__ = PropertyMock(return_value='mockfilename')
        x = mockFUtil.exec_file_path()
        try:
            print (f'Success! Got {x.__file__}.')
        except (ValueError, AttributeError, TypeError) as e:
            print(f'Got a {type(e)} error: {e}')

输出为

C:\Users\Owner\PycharmProjects\Utilities\venv\Scripts\python.exe C:/Users/Owner/.PyCharm2019.2/config/scratches/mockTest.py
Part 1. function returns: C:/Users/Owner/.PyCharm2019.2/config/scratches/mockTest.py
Using patch of __main__.AltFileUtil.
Got a <class 'AttributeError'> error: __file__
Using patch of mockTest.AltFileUtil.
Part 1. function returns: C:\Users\Owner\.PyCharm2019.2\config\scratches\mockTest.py
Using patch of __main__.AltFileUtil.
Got a <class 'AttributeError'> error: __file__
Using patch of mockTest.AltFileUtil.
Got a <class 'AttributeError'> error: __file__
Part 2. Using function from FileUtil.py, which returns: C:\Users\Owner\PycharmProjects\Utilities\FileUtil.py
Using patch of FileUtil.FileUtil.
Got a <class 'AttributeError'> error: __file__
Got a <class 'AttributeError'> error: __file__
Part 2. Using function from FileUtil.py, which returns: C:\Users\Owner\PycharmProjects\Utilities\FileUtil.py
Using patch of FileUtil.FileUtil.
2019-10-08 07:42:13,797 DEBUG Entering exec_file_path.
Got a <class 'AttributeError'> error: __file__
2019-10-08 07:42:13,797 DEBUG >> 0. <FileUtil.FileUtil object at 0x03721230>
2019-10-08 07:42:13,797 DEBUG exec_file_path is about to attempt accessing __file__.
2019-10-08 07:42:13,797 DEBUG >> Return value is C:\Users\Owner\PycharmProjects\Utilities\FileUtil.py.
2019-10-08 07:42:13,797 DEBUG Exiting exec_file_path.
2019-10-08 07:42:13,797 DEBUG Entering exec_file_path.
2019-10-08 07:42:13,797 DEBUG >> 0. <FileUtil.FileUtil object at 0x02E64050>
2019-10-08 07:42:13,797 DEBUG exec_file_path is about to attempt accessing __file__.
2019-10-08 07:42:13,797 DEBUG >> Return value is C:\Users\Owner\PycharmProjects\Utilities\FileUtil.py.
2019-10-08 07:42:13,797 DEBUG Exiting exec_file_path.

Process finished with exit code 0

我认为它应该给我 mockfilename 而不是属性错误。

对于 patch(),重要的是在查找它们的名称空间中修补对象。请参阅文档中的 where to patch

在你的情况下,这是因为行

return __file__

不通过查找实例的属性来解析名称 __file__。它在模块命名空间中解析。

因此,您需要在正确的位置(即在定义了 FileUtil 的模块上)对其进行修补。

我已将 __file__ 语句分解为一个单独的、可修补的成员。

class FileUtil:
    def executing_file(self):
        return globals()['__file__']

    def exec_file_path(self) -> str:
        try:
            ex_file = self.executing_file()
            return ex_file
        except NameError as err:
            logger.debug('Exception message: {msg}'.format(msg=err))
            return abspath('')

以下是我如何修补测试并更改 return 值。

@mock.patch.object(FileUtil, 'executing_file')
def test_exec_file_path(self, mock_obj):
    fUtil = FileUtil()
    mock_obj.return_value = r'C:\mockpath\mockfile.py'
    self.assertEqual(r'C:\mockpath\mockfile.py', fUtil.exec_file_path())

以下是修补它的方法,以便它抛出 NameError(并将使用修补后的 abspath)。

@mock.patch('FileUtil.abspath')
@mock.patch.object(FileUtil, 'executing_file')
def test_exec_file_path_err(self, mock_obj, mock_abs):
    fUtil = FileUtil()
    mock_abs.return_value = r'c:\abspath\mockfile.py'
    mock_obj.side_effect = NameError(mock_obj, 'mock name error')
    self.assertEqual(r'c:\abspath\mockfile.py', fUtil.exec_file_path())