Python 如何根据传递给open()的参数以不同方式模拟open

Python 如何根据传递给open()的参数以不同方式模拟open,python,unit-testing,mocking,file-access,Python,Unit Testing,Mocking,File Access,我的问题是如何在python中模拟open,使其根据调用open()的参数做出不同的反应。以下是一些可能的不同场景: 打开模拟文件;阅读预设内容,基本场景 打开两个模拟文件,让它们为read()方法返回不同的值。打开/读取文件的顺序不应影响结果 此外,如果我调用open('actual_file.txt')来打开一个实际的文件,我希望实际的文件被打开,而不是一个具有模拟行为的魔术模拟。或者,如果我只是不希望对某个文件的访问被模拟,但我确实希望其他文件被模拟,这应该是可能的 我知道这个问题:。

我的问题是如何在python中模拟open,使其根据调用open()的参数做出不同的反应。以下是一些可能的不同场景:

  • 打开模拟文件;阅读预设内容,基本场景
  • 打开两个模拟文件,让它们为read()方法返回不同的值。打开/读取文件的顺序不应影响结果
  • 此外,如果我调用
    open('actual_file.txt')
    来打开一个实际的文件,我希望实际的文件被打开,而不是一个具有模拟行为的魔术模拟。或者,如果我只是不希望对某个文件的访问被模拟,但我确实希望其他文件被模拟,这应该是可能的
我知道这个问题:。
但这个答案只能部分满足第二个要求。不包括与顺序无关的结果部分,它没有指定如何仅模拟某些调用,并允许其他调用进入实际文件(默认行为)。

这可以通过遵循另一个问题的公认答案()中的方法进行一些修改来完成

首先。而不仅仅是指定可以弹出的
副作用
。我们需要确保
副作用
能够根据
打开
调用使用的参数返回正确的模拟文件

然后,如果我们希望打开的文件不在我们希望模拟的文件中,我们将返回文件的原始
open()
,而不是任何模拟行为

下面的代码演示了如何以干净、可重复的方式实现这一点。例如,我将这段代码放在一个文件中,该文件提供了一些实用函数,使测试更容易

from mock import MagicMock
import __builtin__
from mock import patch
import sys

# Reference to the original open function.
g__test_utils__original_open = open
g__test_utils__file_spec = None

def create_file_mock(read_data):

    # Create file_spec such as in mock.mock_open
    global g__test_utils__file_spec
    if g__test_utils__file_spec is None:
        # set on first use
        if sys.version_info[0] == 3:
            import _io
            g__test_utils__file_spec = list(set(dir(_io.TextIOWrapper)).union(set(dir(_io.BytesIO))))
        else:
            g__test_utils__file_spec = file

    file_handle = MagicMock(spec=g__test_utils__file_spec)
    file_handle.write.return_value = None
    file_handle.__enter__.return_value = file_handle
    file_handle.read.return_value = read_data
    return file_handle

def flexible_mock_open(file_map):
    def flexible_side_effect(file_name):
        if file_name in file_map:
            return file_map[file_name]
        else:
            global g__test_utils__original_open
            return g__test_utils__original_open(file_name)

    global g__test_utils__original_open
    return_value = MagicMock(name='open', spec=g__test_utils__original_open)
    return_value.side_effect = flexible_side_effect
    return return_value

if __name__ == "__main__":
    a_mock = create_file_mock(read_data="a mock - content")
    b_mock = create_file_mock(read_data="b mock - different content")
    mocked_files = {
        'a' : a_mock,
        'b' : b_mock,
    }
    with patch.object(__builtin__, 'open', flexible_mock_open(mocked_files)):
        with open('a') as file_handle:
            print file_handle.read() # prints a mock - content

        with open('b') as file_handle:
            print file_handle.read() # prints b mock - different content

        with open('actual_file.txt') as file_handle:
            print file_handle.read() # prints actual file contents
这直接借用了mock.py(Python2.7)中的一些代码来创建文件_spec


旁注:如果有任何机构可以帮助我如何隐藏这些全局变量(如果可能的话),那将非常有帮助。

这可以通过遵循另一个问题的公认答案()中的方法进行一些修改来实现

首先。而不仅仅是指定可以弹出的
副作用
。我们需要确保
副作用
能够根据
打开
调用使用的参数返回正确的模拟文件

然后,如果我们希望打开的文件不在我们希望模拟的文件中,我们将返回文件的原始
open()
,而不是任何模拟行为

下面的代码演示了如何以干净、可重复的方式实现这一点。例如,我将这段代码放在一个文件中,该文件提供了一些实用函数,使测试更容易

from mock import MagicMock
import __builtin__
from mock import patch
import sys

# Reference to the original open function.
g__test_utils__original_open = open
g__test_utils__file_spec = None

def create_file_mock(read_data):

    # Create file_spec such as in mock.mock_open
    global g__test_utils__file_spec
    if g__test_utils__file_spec is None:
        # set on first use
        if sys.version_info[0] == 3:
            import _io
            g__test_utils__file_spec = list(set(dir(_io.TextIOWrapper)).union(set(dir(_io.BytesIO))))
        else:
            g__test_utils__file_spec = file

    file_handle = MagicMock(spec=g__test_utils__file_spec)
    file_handle.write.return_value = None
    file_handle.__enter__.return_value = file_handle
    file_handle.read.return_value = read_data
    return file_handle

def flexible_mock_open(file_map):
    def flexible_side_effect(file_name):
        if file_name in file_map:
            return file_map[file_name]
        else:
            global g__test_utils__original_open
            return g__test_utils__original_open(file_name)

    global g__test_utils__original_open
    return_value = MagicMock(name='open', spec=g__test_utils__original_open)
    return_value.side_effect = flexible_side_effect
    return return_value

if __name__ == "__main__":
    a_mock = create_file_mock(read_data="a mock - content")
    b_mock = create_file_mock(read_data="b mock - different content")
    mocked_files = {
        'a' : a_mock,
        'b' : b_mock,
    }
    with patch.object(__builtin__, 'open', flexible_mock_open(mocked_files)):
        with open('a') as file_handle:
            print file_handle.read() # prints a mock - content

        with open('b') as file_handle:
            print file_handle.read() # prints b mock - different content

        with open('actual_file.txt') as file_handle:
            print file_handle.read() # prints actual file contents
这直接借用了mock.py(Python2.7)中的一些代码来创建文件_spec


旁注:如果有任何机构可以帮助我隐藏这些全局变量(如果可能的话),那将非常有帮助。

有点晚了,但我最近遇到了同样的需要,因此我想分享我的解决方案,基于:

导入pytest
从unittest.mock导入mock_open
从functools导入部分
从pathlib导入路径
模拟文件数据={
“file1.txt”:“一些文本1”,
“file2.txt”:“一些文本2”,
#……等等。。。
}
不要嘲笑:{
#如果需要精确匹配(请参阅mocked_file()中的注释,
#您应该用正确的Path()调用替换它们
“notmocked1.txt”,
“notmocked2.txt”,
#……等等。。。
}
#参考:https://stackoverflow.com/a/38618056/149900
def模拟_文件(m,fn,*args,**kwargs):
m、 打开的文件=路径(fn)
fn=路径(fn).name#如果需要精确的路径匹配,请删除此行
如果fn在do_not_mock中:
返回打开(fn、*args、**kwargs)
如果fn不在模拟文件数据中:
raise FileNotFoundError
数据=模拟文件数据[fn]
文件对象=模拟打开(读取数据=数据)。返回值
文件对象返回值=数据分割线(真)
返回文件
def断言_已打开(m,fn):
fn=路径(fn)
断言m.opened_文件==fn
@pytest.fixture()
def模拟打开(模拟器):
m=mocker.patch(“内置打开”)
m、 副作用=部分(模拟文件,m)
m、 断言已打开=部分(断言已打开,m)
返回m
def测试(模拟打开):
...
#不应调用open()的内容
mocked_open.assert_not_called()
...
#应该调用open()的东西
mocked_open.assert_called_once()
mocked_open.assert_open(“file1.txt”)
#取决于被测试单元如何处理“裸”文件名,
#您可能必须将参数更改为:
#Path.cwd()/“file1.txt”
#……等等。。。

请注意:(1)我正在使用Python 3,(2)我正在使用
pytest

有点晚,但我最近遇到了同样的需求,因此我想分享我的解决方案,基于:

导入pytest
从unittest.mock导入mock_open
从functools导入部分
从pathlib导入路径
模拟文件数据={
“file1.txt”:“一些文本1”,
“file2.txt”:“一些文本2”,
#……等等。。。
}
不要嘲笑:{
#如果需要精确匹配(请参阅mocked_file()中的注释,
#您应该用正确的Path()调用替换它们
“notmocked1.txt”,
“notmocked2.txt”,
#……等等。。。
}
#参考:https://stackoverflow.com/a/38618056/149900
def模拟_文件(m,fn,*args,**kwargs):
m、 打开的文件=路径(fn)
fn=路径(fn).name#如果需要精确的路径匹配,请删除此行
如果fn在do_not_mock中:
返回打开(fn、*args、**kwargs)
如果fn不在模拟文件数据中:
raise FileNotFoundError
数据=模拟文件数据[fn]
文件对象=模拟打开(读取数据=数据)。返回值
文件对象返回值=数据分割线(真)
返回文件
def断言_已打开(m,fn):
fn=路径(fn)
断言m.opened_文件==fn
@pytest.fixture()
def模拟打开(模拟器):