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

buh*_*htz 3 python unit-testing mocking python-3.x

我在我的生产代码中使用pathlib.Path.open()and 。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):
Run Code Online (Sandbox Code Playgroud)

但目前真正的代码看起来像这样

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)
Run Code Online (Sandbox Code Playgroud)

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

它可能看起来像这样:

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)
Run Code Online (Sandbox Code Playgroud)

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

aar*_*ron 5

出于学习目的

问:现在如何将“opener”和“mymock_open”结合在一起?

A:将side_effectreturn_value赋值mymock_open给 的那些opener

@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)
Run Code Online (Sandbox Code Playgroud)

但这很难说是可读性的提高。

两者都使用装饰器

@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)
Run Code Online (Sandbox Code Playgroud)

仅传递mock.mock_open(read_data='A;B\n1;2')(作为位置参数new)而不是new_callable=lambda: ...也可以,但@mock.patch不会传递mock_opentest_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)
Run Code Online (Sandbox Code Playgroud)

请注意,这mock_open与 是同一个实例opener


验证解决方案

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'
Run Code Online (Sandbox Code Playgroud)