如何将 pyfakefs 与 importlib.resources.path 结合使用

How to use pyfakefs in conjunction with importlib.resources.path

给定一些已安装的包,以下代码可用于打印其在文件系统中的位置:

import importlib.resources

def print_package_path():
    with importlib.resources.path("importlib", "") as path:
        print(path)

if __name__ == "__main__":
    print_package_path()  # /home/me/python_3.8/lib/python3.8/importlib on my machine

如果我想测试一个函数,其中包含这样一个语句 pytest 作为测试套件和 pyfakefs 在测试期间伪造文件系统,它会崩溃并出现令人困惑的错误 (IsADirectoryError: [Errno 21] Is a directory on ubuntu and PermissionError: [Errno 13] Permission denied on windows) - 甚至不需要对 fs fixture 做任何事情,它只需要在代码出现时就在那里运行.

pytest执行这个函数

def test_package_path(fs):
    print_package_path()

将导致

______________________________ test_package_path _______________________________

fs = <pyfakefs.fake_filesystem.FakeFilesystem object at 0x7f84f2996910>

    def test_package_path(fs):
>       print_package_path()

/home/me/foo.py:11: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/home/me/foo.py:4: in print_package_path
    with importlib.resources.path("importlib", "") as path:
/home/me/pythons/python_3.8/lib/python3.8/contextlib.py:113: in __enter__
    return next(self.gen)
/home/me/venv/lib/python3.8/importlib/resources.py:201: in path
    with open_binary(package, resource) as fp:
/home/me/venv/lib/python3.8/importlib/resources.py:91: in open_binary
    return reader.open_resource(resource)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <_frozen_importlib_external.SourceFileLoader object at 0x7f84f7527370>
resource = ''

>   ???
E   IsADirectoryError: [Errno 21] Is a directory: '/home/me/venv/lib/python3.8/importlib'

<frozen importlib._bootstrap_external>:988: IsADirectoryError

这对我来说毫无意义。目录从一开始就不应该用作资源吗?

我必须承认我并不真正理解 importlib.resources 模块,所以我可能只是用错了它(我的实际用例是在开发过程中创建一个文件,并避免使用 __file__找到正确的位置)。

参见 pyfakefs documentation 指出:

Modules may not work with pyfakefs for several reasons. pyfakefs works by patching some file system related modules and functions, specifically:

  • most file system related functions in the os and os.path modules
  • the pathlib module
  • the build-in open function and io.open
  • shutil.disk_usage

只需包含 fs 夹具即可修补这些模块。如果您确实需要使用 pyfakefs,要么提供您的代码期望的所有内容(甚至是间接的),要么使用 paused fs 开始您的测试并仅启用它来测试无法通过其他方式测试的特定内容。在这种情况下,中断的是 io.open


通过在执行依赖于文件存在的函数之前调用 fs.add_real_directory 来提供预期的文件,如下所示:

def test_package_path(fs):
    fs.add_real_directory(os.path.dirname(importlib.__file__))
    print_package_path()

,其中 importlib.__file__ 需要替换为测试代码中 importlib.resources.path 访问的任何内容的完整路径。此方法对于在测试函数中创建文件也是安全的,因为假 fs 知道所有更改并且从不将它们应用于实际文件:

The access to the files is by default read-only, but even if you add them using read_only=False, the files are written only in the fake system (e.g. in memory). The real files are never changed.