如何使用相同的语法模拟 pathlib.Path.open 和 pathlib.path.unlink?

How can I mock pathlib.Path.open and pathlib.path.unlink with the same syntax?

我在生产代码中使用 pathlib.Path.open()pathlib.Path.unlink()。该作品的 unittest。但是我用两种不同的方式来patch()。一个带有 @patch 装饰器,一个带有上下文管理器 with mock.patch().

我只想@patch这样。

class MyTest(unittest.TestCase):
    @mock.patch('pathlib.Path.unlink')
    @mock.patch('pathlib.Path.open')
    def test_foobar(self, mock_open, mock_unlink):

但是当前的真实代码是这样的

import unittest
from unittest import mock
import pathlib

class MyTest(unittest.TestCase):
    @mock.patch('pathlib.Path.unlink')
    def test_foobar(self, mock_unlink):
        # simulated CSV file
        opener = mock.mock_open(read_data='A;B\n1;2')

        with mock.patch('pathlib.Path.open', opener):
            result = validate_csv(file_path=pathlib.Path('foo.csv'))
            self.assertTrue(result)

我的技术问题是,在使用 @patch 装饰器时,我不知道如何将我的 CSV 内容添加到 mock_open

它可能看起来像这样:

class MyTest(unittest.TestCase):
    @mock.patch('pathlip.Path.open')
    @mock.patch('pathlib.Path.unlink')
    def test_foobar(self, mymock_unlink, mymock_open):
        # simulated CSV file
        opener = mock.mock_open(read_data='A;B\n1;2')

        # QUESTION: How do I bring 'opener' and 'mymock_open'
        # together now?

        result = validate_csv(file_path=pathlib.Path('foo.csv'))
        self.assertTrue(result)

但是我的问题的目的是提高代码的可读性和可维护性。使用两个装饰器可以减少缩进。选择一种方式(装饰器或上下文管理器)恕我直言,更容易阅读。

出于学习目的

问:现在如何将 'opener' 和 'mymock_open' 放在一起?

A: 将 mymock_openside_effectreturn_value 分配给 openerside_effectreturn_value

@mock.patch('pathlib.Path.open')
@mock.patch('pathlib.Path.unlink')
def test_foobar(self, mymock_unlink, mymock_open):
    # simulated CSV file
    opener = mock.mock_open(read_data='A;B\n1;2')

    # QUESTION: How do I bring 'opener' and 'mymock_open'
    # together now?
    mymock_open.side_effect = opener.side_effect    # +
    mymock_open.return_value = opener.return_value  # +

    result = validate_csv(file_path=pathlib.Path('foo.csv'))
    opener.assert_not_called()          # +
    mymock_open.assert_called_once()    # +
    mymock_unlink.assert_called_once()  # +
    self.assertTrue(result)

但这算不上可读性的提高。

都使用装饰器

@mock.patch('pathlib.Path.open', new_callable=lambda: mock.mock_open(read_data='A;B\n1;2'))  # +
@mock.patch('pathlib.Path.unlink')
def test_foobar(self, mock_unlink, mock_open):
    result = validate_csv(file_path=pathlib.Path('foo.csv'))
    mock_open.assert_called_once()    # +
    mock_unlink.assert_called_once()  # +
    self.assertTrue(result)

仅传递 mock.mock_open(read_data='A;B\n1;2')(作为位置参数 new)而不是 new_callable=lambda: ... 也可以,但是 @mock.patch 不会将 mock_open 传递给 test_foobar.

两者都使用上下文管理器

def test_foobar(self):
    # simulated CSV file
    opener = mock.mock_open(read_data='A;B\n1;2')

    with mock.patch('pathlib.Path.unlink') as mock_unlink,\
            mock.patch('pathlib.Path.open', opener) as mock_open:  # +
        self.assertIs(mock_open, opener)  # +
        result = validate_csv(file_path=pathlib.Path('foo.csv'))
        mock_open.assert_called_once()    # +
        mock_unlink.assert_called_once()  # +
        self.assertTrue(result)

请注意 mock_openopener 是同一个实例。


验证解决方案

validate_csv 的最小可重现示例的实施示例:

def validate_csv(file_path):
    """
    :param pathlib.Path file_path:
    :rtype: bool
    """
    with file_path.open() as f:
        data = f.read()
    file_path.unlink()
    return data == 'A;B\n1;2'