【问题标题】:Mocking file reads using patch decorator and side_effect使用补丁装饰器和 side_effect 模拟文件读取
【发布时间】:2017-08-05 02:04:16
【问题描述】:

我正在测试一个函数,它读取文件、对内容进行操作并根据它看到的内容返回一个值。我测试的函数testme 位于module.py。我正在运行 python 2.7。我知道我可以做到这一点

import unittest
import module
from mock import patch, mock_open

TestTestMe(unittest.TestCase):
    ...
    def test_test_me(self):
        with patch('module.open', mock_open(read_data='1 2')) as _:
          self.assertRaises(IndexError, module.testme, 'foo')
        with patch('module.open', mock_open(read_data='1 2 3')) as _:
          self.assertEquals(module.testme('foo'), 3)

等等

但是,我希望(主要是为了防止重复使用 with 语句,并且能够动态生成各种 read_data)能够使用 @patch 作为装饰器来定义我的 read_data 函数。像这样的东西。我不会重复类定义和导入。

def give_contents(x):
    if x == 'correct':
        return mock_open(read_data='1 2 3')
    else:
        return mock_open(read_data='a b c')

然后使用如下测试函数:

@patch(module.open, side_effect=give_contents)
def test_test_me(self):
    self.assertEquals(module.testme('correct'), 3)

我不断遇到诸如

之类的 TypeErrors
TypeError: test_testme() takes exactly 1 argument (2 given)

但是我试图解决这个问题。这真让我抓狂。指导将不胜感激。如果您想要一些我可能省略的其他细节,请询问具体细节,我会提供这些。

编辑:按要求执行要测试的功能。很抱歉,我因为“不重要”而忽略了它,它显然应该在那里。

def testme(filepath):
    with open(filepath, 'r') as f:
        line = f.readline().strip().split()
    return int(line[2])

【问题讨论】:

  • 我不确定,因为你没有在你给我们的代码中包含任何出现的 test_testme 函数。但是,如果您使用的是module.testme 方法,您忘记在方法定义中声明字符串参数。根据您的反馈,我可能会将此作为答案。
  • 请考虑不要在这里嘲笑。您最好将“文件”对象转换为参数并让其他未进行单元测试的代码打开文件。然后,您可以将某种字符串 IO 对象作为存根传入。即使你不能使用字符串 IO,你仍然可以只传递一个模拟作为参数。 (即使我使用了模拟,我也可能不会断言。)
  • @Alceste_ 我添加了实现。如果您写了完整的答案,那就太好了。
  • 那么,我会的,虽然对于一个完整的答案来说似乎很短。

标签: python unit-testing testing mocking


【解决方案1】:

我会考虑以下几点:

from io import TextIOWrapper, BytesIO
from unittest.mock import patch, mock_open

def open_file(filename):
    with open(filename, 'r') as f:
        data = f.readline()
    return data

def string_byte_me(input):
    return TextIOWrapper(BytesIO(input))

def side_effect(filename, mode):
    if filename == 'correct':
        return string_byte_me(b'1 2 3')
    else:
        return string_byte_me(b'a b c')

m = mock_open()
m.side_effect = side_effect
@patch('builtins.open', m)
def test_open_file():
    assert open_file('correct') == '1 2 3'
    assert open_file('incorrect') == 'a b c'
test_open_file() # Passes.

这通过在实例化后向 mock_open 对象添加 side_effect 来工作(不确定是否有更好的方法?)。返回的 side_effect 必须能够 .readline() 因此 TextIOWrapper。

【讨论】:

  • StringIO(input) 代替 TextIOWrapper(BytesIO(input)) 但这真的很有帮助。
【解决方案2】:

正如我在之前的评论中所说: 我不确定这一点,因为您在提供给我们的代码中没有包含任何 test_testme 函数的出现。但是,如果它是您使用的 module.testme 方法,您忘记在方法定义中声明字符串参数。根据您的反馈,我可能会将此作为答案。

编辑:我并不完全在现场,因为你忘记的论点是自我。

显然,这对你有用,所以这是承诺的答案。

假设你谈到的 module.testme 方法是一个看起来像这样的函数:

TestTestMe(unittest.TestCase):
...
def testme(filepath):
with open(filepath, 'r') as f:
    line = f.readline().strip().split()
return int(line[2])

然而,这个函数更像是一个方法,因为你是从一个对象访问它。 (执行module.testme('foo')),因此,调用的第一个参数将始终是隐含的self

所以发生的情况是,您的函数需要一个参数,一个字符串 ('foo'),但即使 self 不明确,也会给出两个参数:'(self, 'foo')'。

因此出现错误,表示您收到的参数比您要求的要多。 更正非常简单:将 self 添加到 testme 的预期参数中。

然后会变成:

def testme(self, filepath):
    with open(filepath, 'r') as f:
        line = f.readline().strip().split()
    return int(line[2])

希望这会有所帮助。关于论点数量的错误通常是由于这种被遗忘的细节。虽然你不需要 self,但它总是作为第一个位置参数传递(在 python 中)。

祝你有美好的一天! PS:对于一些奇怪的英语措辞和重复感到抱歉。

【讨论】:

    猜你喜欢
    • 2018-12-30
    • 2021-12-20
    • 2022-12-09
    • 1970-01-01
    • 1970-01-01
    • 2017-01-31
    • 2021-07-19
    • 2021-11-21
    • 1970-01-01
    相关资源
    最近更新 更多