Tom*_* P. 2 python unit-testing mocking file-access
我的问题是如何在 python 中模拟 open ,以便它根据调用 open() 的参数做出不同的反应。这些是一些可能的不同场景:
open('actual_file.txt')
打开一个实际文件,我希望打开实际文件,而不是具有模拟行为的魔术模拟。或者,如果我只是不想模拟对某个文件的访问,但我确实希望模拟其他文件,那么这应该是可能的。我知道这个问题:Python mockbuiltin 'open' in a class using two different files。但这个答案只能部分满足第二个要求。不包括有关顺序独立结果的部分,并且它没有指定如何仅模拟某些调用,并允许其他调用进入实际文件(默认行为)。
有点晚了,但我最近遇到了同样的需求,所以我想根据所提到的问题的答案来分享我的解决方案:
import pytest
from unittest.mock import mock_open
from functools import partial
from pathlib import Path
mock_file_data = {
"file1.txt": "some text 1",
"file2.txt": "some text 2",
# ... and so on ...
}
do_not_mock: {
# If you need exact match (see note in mocked_file(),
# you should replace these with the correct Path() invocations
"notmocked1.txt",
"notmocked2.txt",
# ... and so on ...
}
# Ref: https://stackoverflow.com/a/38618056/149900
def mocked_file(m, fn, *args, **kwargs):
m.opened_file = Path(fn)
fn = Path(fn).name # If you need exact path match, remove this line
if fn in do_not_mock:
return open(fn, *args, **kwargs)
if fn not in mock_file_data:
raise FileNotFoundError
data = mock_file_data[fn]
file_obj = mock_open(read_data=data).return_value
file_obj.__iter__.return_value = data.splitlines(True)
return file_obj
def assert_opened(m, fn):
fn = Path(fn)
assert m.opened_file == fn
@pytest.fixture()
def mocked_open(mocker):
m = mocker.patch("builtins.open")
m.side_effect = partial(mocked_file, m)
m.assert_opened = partial(assert_opened, m)
return m
def test_something(mocked_open):
...
# Something that should NOT invoke open()
mocked_open.assert_not_called()
...
# Something that SHOULD invoke open()
mocked_open.assert_called_once()
mocked_open.assert_opened("file1.txt")
# Depends on how the tested unit handle "naked" filenames,
# you might have to change the arg to:
# Path.cwd() / "file1.txt"
# ... and so on ...
Run Code Online (Sandbox Code Playgroud)
请注意,(1) 我使用的是 Python 3,(2) 我使用的是pytest
.