【问题标题】:How do I mock the filesystem in Python unit tests?如何在 Python 单元测试中模拟文件系统?
【发布时间】:2013-11-09 10:18:32
【问题描述】:

是否有标准方法(无需安装第三方库)在 Python 中进行跨平台文件系统模拟?如果我必须使用第三方库,哪个库是标准的?

【问题讨论】:

  • filesystem 的概念太宽泛了。这可能是任何事情。你到底想要什么?
  • @Leonardo.Z:与文件系统的任何交互。我最关心的是创建打开删除文件和目录。在其他语言中,可以模拟整个文件系统。
  • tempfile 模块会有帮助吗?

标签: python unit-testing mocking filesystems


【解决方案1】:

Python 3.3+ 中的标准模拟框架是unittest.mock;您可以将它用于文件系统或其他任何东西。

您也可以通过猴子补丁模拟来简单地手动滚动它:

一个简单的例子:

import os.path
os.path.isfile = lambda path: path == '/path/to/testfile'

更完整一点(未经测试):

import classtobetested                                                                                                                                                                                      
import unittest                                                                                                                                                                                             

import contextlib                                                                                                                                                                                           

@contextlib.contextmanager                                                                                                                                                                                  
def monkey_patch(module, fn_name, patch):                                                                                                                                                                   
    unpatch = getattr(module, fn_name)                                                                                                                                                                      
    setattr(module, fn_name)                                                                                                                                                                                
    try:                                                                                                                                                                                                    
        yield                                                                                                                                                                                               
    finally:                                                                                                                                                                                                
        setattr(module, fn_name, unpatch)                                                                                                                                                                   


class TestTheClassToBeTested(unittest.TestCase):                                                                                                                                                              
    def test_with_fs_mocks(self):                                                                                                                                                                           
        with monkey_patch(classtobetested.os.path,                                                                                                                                                          
                          'isfile',                                                                                                                                                                         
                          lambda path: path == '/path/to/file'):                                                                                                                                            
            self.assertTrue(classtobetested.testable())                 

在这个例子中,实际的模拟是微不足道的,但你可以用一些有状态的东西来支持它们,这样就可以代表文件系统操作,比如保存和删除。是的,这有点难看,因为它需要在代码中复制/模拟基本文件系统。

请注意,您不能猴子修补 python 内置函数。话说……

对于早期版本,如果可能使用第三方库,我会选择 Michael Foord 的出色 Mock,由于 PEP 0417,它现在是标准库中的 unittest.mock,因为 3.3+对于 Python 2.5+,可以在 PyPI 上获得它。而且,它可以模拟内置函数!

【讨论】:

    【解决方案2】:

    pyfakefs (homepage) 做你想做的事——一个 fake 文件系统;它是第三方,尽管该方是谷歌。有关使用的讨论,请参阅How to replace file-access references for a module under test

    对于 mockingunittest.mock 是 Python 3.3+ 的标准库 (PEP 0417);对于早期版本,请参阅PyPI: mock(对于 Python 2.5+)(homepage)。

    测试和模拟中的术语不一致;使用 Gerard Meszaros 的 Test Double 术语,您要求的是“假”:行为类似于文件系统的东西(您可以创建、打开和删除文件),但不是实际的文件系统(在这种情况下它在内存中),因此您不需要有测试文件或临时目录。

    在经典模拟中,您将改为 模拟 系统调用(在 Python 中,模拟 os 模块中的函数,例如 os.rmos.listdir),但不仅如此繁琐。

    【讨论】:

    • 关于compatibility of pyfakefs 的重要说明:“pyfakefs 无法与使用 C 库访问文件系统的 Python 库一起使用。这是因为 pyfakefs 无法修补底层 C 库的文件访问功能-- C 库将始终访问真实的文件系统。例如,pyfakefs 不适用于 lxml。在这种情况下,lxml 必须替换为纯 Python 替代品,例如 xml.etree.ElementTree。"
    【解决方案3】:

    pytest 获得了很大的吸引力,它可以使用tmpdirmonkeypatching 完成所有这些工作(嘲笑)。

    您可以使用tmpdir 函数参数,该参数将提供一个对测试调用唯一的临时目录,在基本临时目录中创建(默认情况下创建为系统临时目录的子目录)。

    import os
    def test_create_file(tmpdir):
        p = tmpdir.mkdir("sub").join("hello.txt")
        p.write("content")
        assert p.read() == "content"
        assert len(tmpdir.listdir()) == 1
    

    monkeypatch 函数参数可帮助您安全地设置/删除属性、字典项或环境变量,或修改 sys.path 以进行导入。

    import os
    def test_some_interaction(monkeypatch):
        monkeypatch.setattr(os, "getcwd", lambda: "/")
    

    你也可以传递一个函数而不是使用 lambda。

    import os.path
    def getssh(): # pseudo application code
        return os.path.join(os.path.expanduser("~admin"), '.ssh')
    
    def test_mytest(monkeypatch):
        def mockreturn(path):
            return '/abc'
        monkeypatch.setattr(os.path, 'expanduser', mockreturn)
        x = getssh()
        assert x == '/abc/.ssh'
    
    # You can still use lambda when passing arguments, e.g.
    # monkeypatch.setattr(os.path, 'expanduser', lambda x: '/abc')
    

    如果您的应用程序与文件系统有很多交互,那么使用 pyfakefs 之类的东西可能会更容易,因为模拟会变得乏味和重复。

    【讨论】:

      【解决方案4】:

      伪装还是嘲讽?

      就我个人而言,我发现文件系统中存在很多边缘情况(例如以正确的权限打开文件、字符串与二进制文件、读/写模式等),并且使用准确的假文件系统可以找到很多你可能无法通过模拟找到的错误。在这种情况下,我会查看pyfilesystemmemoryfs 模块(它具有相同接口的各种具体实现,因此您可以在代码中将它们换掉)。

      模拟(并且没有猴子补丁!):

      也就是说,如果你真的想模拟,你可以使用 Python 的 unittest.mock 库轻松做到这一点:

      # production code file; note the default parameter
      def make_hello_world(path, open_func=open):
          with open_func(path, 'w+') as f:
              f.write('hello, world!')
      
      # test code file
      def test_make_hello_world():
          file_mock = unittest.mock.Mock(write=unittest.mock.Mock())
          open_mock = unittest.mock.Mock(return_value=file_mock)
      
          # When `make_hello_world()` is called
          make_hello_world('/hello/world.txt', open_func=open_mock)
      
          # Then expect the file was opened and written-to properly
          open_mock.assert_called_once_with('/hello/world.txt', 'w+')
          file_mock.write.assert_called_once_with('hello, world!')
      

      以上示例仅演示了通过模拟open() 方法创建和写入文件,但您可以轻松模拟任何方法。

      【讨论】:

        猜你喜欢
        • 2018-08-07
        • 2010-11-08
        • 2011-10-04
        • 1970-01-01
        • 2015-02-27
        • 2020-02-13
        • 2017-03-04
        • 1970-01-01
        相关资源
        最近更新 更多