模拟open()函数在类方法中使用

时间:2015-11-11 12:29:03

标签: python unit-testing mocking

我试图模拟我班级方法中使用的open函数。 我找到了这个帖子How do I mock an open used in a with statement (using the Mock framework in Python)?,但无法解决我的问题。单元测试文档也显示了一个解决方案,它也没有模拟我的开放https://docs.python.org/3/library/unittest.mock-examples.html#patch-decorators

这是我的类,其中使用了open函数的方法:

#__init.py__

import json

class MyClass:

    def save_data_to_file(self, data):
        with open('/tmp/data.json', 'w') as file:
            json.dump(data, file)
...
mc = MyClass()

现在我找到了一个不同的解决方案。这是我的考验:

#save_to_file_test.py

from mymodule import MyClass
from mock import mock_open, patch
import ast

class SaveToFileTest(unittest.TestCase):

    def setUp(self):
        self.mc = MyClass()
        self.data = [
            {'id': 5414470, 'name': 'peter'},
            {'id': 5414472, 'name': 'tom'},
            {'id': 5414232, 'name': 'pit'},
        ]

    def test_save_data_to_file(self):
        m = mock_open()
        with patch('mymodule.open', m, create=True):
            self.mc.save_data_to_file(self.data)
            string = ''
            for call in m.return_value.write.mock_calls:
                string += (call[1][0])
            list = ast.literal_eval(string)
            assertEquals = (list, self.data)

我不确定这是否是测试应该写入文件的内容的最佳方法。 当我测试mock_calls(call_args_list是相同的)时,这是传递给文件句柄的参数。 欢迎任何建议,改进和建议。

1 个答案:

答案 0 :(得分:20)

TL; DR

问题的核心在于 还应该嘲笑json.dump,以便能够正确测试要写入文件的数据。实际上我很难运行代码,直到对您的测试方法进行了一些重要的调整。

  • 模拟builtins.open而非mymmodule.open
  • 您在上下文管理器中,因此您应该检查m.return_value.__enter__.write,但是您实际上正在调用json.dump中的写入,这是写入的位置。 (以下详细介绍建议的解决方案)
  • 您还应该模拟json.dump以简单地验证是否使用您的数据调用

简而言之,对于上述问题,该方法可以重写为:

以下所有内容的详细信息

def test_save_data_to_file(self):
    with patch('builtins.open', new_callable=mock_open()) as m:
        with patch('json.dump') as m_json:
            self.mc.save_data_to_file(self.data)

            # simple assertion that your open was called 
            m.assert_called_with('/tmp/data.json', 'w')

            # assert that you called m_json with your data
            m_json.assert_called_with(self.data, m.return_value)

详细说明

要专注于我在代码中看到的问题,我强烈建议做的第一件事,因为open是内置的,是来自内置的模拟,此外,你可以通过制作自己保存一行代码使用new_callableas,您只需执行此操作:

with patch('builtins.open', new_callable=mock_open()) as m:

我在您的代码中看到的下一个问题,因为我在运行此操作时遇到问题,直到您开始循环调用时实际进行了以下调整:

m.return_value.__enter__.return_value.write.mock_calls

要剖析它,您必须记住的是您的方法正在使用上下文管理器。在使用上下文管理器时,您的写入工作实际上将在__enter__方法中完成。因此,从return_value的{​​{1}}开始,您希望获得m的return_value。

但是,这会让我们了解您要测试的问题的核心。由于__enter__在写入文件时的工作原理,在检查代码后,您的json.dump代码实际上看起来像这样:

mock_calls

测试不会很有趣。因此,这将我们带到您可以尝试的下一个解决方案;模仿<MagicMock name='open().write' id='4348414496'> call('[') call('{') call('"name"') call(': ') call('"peter"') call(', ') call('"id"') call(': ') call('5414470') call('}') call(', ') call('{') call('"name"') call(': ') call('"tom"') call(', ') call('"id"') call(': ') call('5414472') call('}') call(', ') call('{') call('"name"') call(': ') call('"pit"') call(', ') call('"id"') call(': ') call('5414232') call('}') call(']') call.__str__()

你不应该测试json.dump,你应该测试用正确的参数调用它。话虽如此,你可以按照类似的方式与你的嘲笑,并做这样的事情:

json.dump

现在,通过它,您可以显着简化测试代码,只需验证使用您正在测试的数据调用该方法。所以,有了它,当你把它们放在一起时,你会有这样的东西:

with patch('json.dump') as m_json:

如果您对进一步重构感兴趣以使您的测试方法更清洁,您还可以将修补程序设置为装饰器,将代码更清晰地放在方法中:

def test_save_data_to_file(self):
    with patch('builtins.open', new_callable=mock_open()) as m:
        with patch('json.dump') as m_json:
            self.mc.save_data_to_file(self.data)

            # simple assertion that your open was called 
            m.assert_called_with('/tmp/data.json', 'w')

            # assert that you called m_json with your data
            m_json.assert_called_with(self.data, m.return_value.__enter__.return_value)

在这里检查是你最好的朋友,看看在什么步骤中调用了什么方法,以进一步帮助测试。祝好运。