如何在 Python 单元测试中模拟文件系统?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/19672138/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-19 14:19:29  来源:igfitidea点击:

How do I mock the filesystem in Python unit tests?

pythonunit-testingmockingfilesystems

提问by DudeOnRock

Is there a standard way (without installing third party libraries) to do cross platform filesystem mocking in Python? If I have to go with a third party library, which library is the standard?

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

采纳答案by Nils von Barth

pyfakefs(homepage) does what you want – a fakefilesystem; it's third-party, though that party is Google. See How to replace file-access references for a module under testfor discussion of use.

pyfakefs主页)做你想做的——一个文件系统;它是第三方,尽管该方是 Google。有关使用的讨论,请参阅如何替换被测模块的文件访问引用

For mocking, unittest.mockis the standard library for Python 3.3+ (PEP 0417); for earlier version see PyPI: mock(for Python 2.5+) (homepage).

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

Terminology in testing and mocking is inconsistent; using the Test Doubleterminology of Gerard Meszaros, you're asking for a “fake”: something that behaves like a filesystem (you can create, open, and delete files), but isn't the actual file system (in this case it's in-memory), so you don't need to have test files or a temporary directory.

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

In classic mocking, you would instead mockout the system calls (in Python, mock out functions in the osmodule, like os.rmand os.listdir), but that's much more fiddly.

在经典模拟中,您会模拟系统调用(在 Python 中,模拟os模块中的函数,如os.rmos.listdir),但这要繁琐得多。

回答by jtmoulia

The standard mocking framework in Python 3.3+ is unittest.mock; you can use this for the filesystem or anything else.

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

You could also simply hand roll it by mocking via monkey patching:

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

A trivial example:

一个简单的例子:

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

A bit more full (untested):

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

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())                 

In this example, the actual mocks are trivial, but you could back them with something that has state so that can represent filesystem actions, such as save and delete. Yes, this is all a bit ugly since it entails replicating/simulating basic filesystem in code.

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

Note that you can't monkey patch python builtins. That being said...

请注意,您不能对 python 内置程序进行猴子修补。话虽如此...

For earlier versions, if at all possible use a third party library, I'd go with Michael Foord's awesome Mock, which is now unittest.mockin the standard library since 3.3+ thanks to PEP 0417, and you can get it on PyPIfor Python 2.5+. And, it can mock builtins!

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

回答by Dennis

pytestis gaining a lot of traction, and it can do all of this using tmpdirand monkeypatching(mocking).

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

You can use the tmpdirfunction argument which will provide a temporary directory unique to the test invocation, created in the base temporary directory (which are by default created as sub-directories of the system temporary directory).

您可以使用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

The monkeypatchfunction argument helps you to safely set/delete an attribute, dictionary item or environment variable or to modify sys.pathfor importing.

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

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

You can also pass it a function instead of using 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')

If your application has a lot of interaction with the file system, then it might be easier to use something like pyfakefs, as mocking would become tedious and repetitive.

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

回答by weberc2

Faking or Mocking?

假装还是嘲讽?

Personally, I find that there are a lot of edge cases in filesystem things (like opening the file with the right permissions, string-vs-binary, read/write mode, etc), and using an accurate fake filesystem can find a lot of bugs that you might not find by mocking. In this case, I would check out the memoryfsmodule of pyfilesystem(it has various concrete implementations of the same interface, so you can swap them out in your code).

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

Mocking (and without Monkey Patching!):

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

That said, if you really want to mock, you can do that easily with Python's unittest.mocklibrary:

也就是说,如果你真的想模拟,你可以用 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!')

The above example only demonstrates creating and writing to files via mocking the open()method, but you could just as easily mock any method.

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