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

Tom*_* P. 2 python unit-testing mocking file-access

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

  • 打开一个模拟文件;阅读预设内容,基本场景。
  • 打开两个模拟文件并让它们为 read() 方法返回不同的值。打开/读取文件的顺序不应影响结果。
  • 此外,如果我调用open('actual_file.txt')打开一个实际文件,我希望打开实际文件,而不是具有模拟行为的魔术模拟。或者,如果我只是不想模拟对某个文件的访问,但我确实希望模拟其他文件,那么这应该是可能的。

我知道这个问题:Python mockbuiltin 'open' in a class using two different files。但这个答案只能部分满足第二个要求。不包括有关顺序独立结果的部分,并且它没有指定如何仅模拟某些调用,并允许其他调用进入实际文件(默认行为)。

pep*_*uan 6

有点晚了,但我最近遇到了同样的需求,所以我想根据所提到的问题的答案分享我的解决方案:

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.