类方法中使用的模拟open()函数 [英] Mock open() function used in a class method

查看:109
本文介绍了类方法中使用的模拟open()函数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图模拟类方法中使用的open函数. 我发现此线程

I tried to mock the open function used in a method of my class. I found this thread How do I mock an open used in a with statement (using the Mock framework in Python)? but could not solve my issue. Also the unittest documention shows a solution which also didn't mock my open https://docs.python.org/3/library/unittest.mock-examples.html#patch-decorators

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

This is my class with the method where the open function is used:

#__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()

现在,我找到了一些不同的解决方案.这是我的测试:

Now I found a little different solution. This is my test:

#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是相同的)时,这是传递到文件句柄的参数. 欢迎任何建议,改进和建议.

I'm not sure if this is the best way to test the content which should be written to a file. When I test the mock_calls (call_args_list is the same) this are the arguments which are passed to the file handle. Any advice, improvements and suggestions are welcome.

推荐答案

TL; DR

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

TL;DR

The heart of your problem is that you should be also mocking json.dump to be able to properly test the data that is going to be written to your file. I actually had a hard time running your code until a few important adjustments were made to your test method.

  • 模拟builtins.open而不是mymmodule.open
  • 您在上下文管理器中,因此应检查m.return_value.__enter__.write,但是实际上是从json.dump调用写操作,而写操作将在该位置调用. (下面有关建议的解决方案的详细信息)
  • 您还应该模拟json.dump以简单地验证它是否与您的数据一起调用
  • Mock with builtins.open and not mymmodule.open
  • You are in a context manager, so you should be checking m.return_value.__enter__.write, however you are actually calling the write from json.dump which is where the write will be called. (Details below on a suggested solution)
  • You should also mock json.dump to simply validate it is called with your data

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

In short, with the issues mentioned above, the method can be re-written as:

以下所有详细信息

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,因此您只需执行以下操作:

Detailed Explanation

To focus on the problems I see in your code, the first thing I strongly suggest doing, since open is a builtin, is to mock from builtins, furthermore, you can save yourself a line of code by making use of new_callable and as, so you can simply do this:

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

我遇到的下一个问题是代码运行困难,直到您开始循环调用时我实际上进行了以下调整:

The next problem that I see with your code as I had trouble running this until I actually made the following adjustment when you started looping over your calls:

m.return_value.__enter__.return_value.write.mock_calls

要对此进行剖析,必须牢记的是您的方法正在使用上下文管理器.使用上下文管理器时,您的写操作实际上将在__enter__方法内部完成.因此,您想从mreturn_value中获取__enter__的return_value.

To dissect that, what you have to keep in mind is that your method is using a context manager. In using a context manager, the work of your write will actually be done inside your __enter__ method. So, from the return_value of your m, you want to then get the return_value of __enter__.

但是,这将我们带到了您要测试的问题的核心.由于json.dump写入文件时的工作方式,因此mock_calls用于检查代码后的写入实际上将如下所示:

However, this brings us to the heart of the problem with what you are trying to test. Because of how the json.dump works when writing to the file, your mock_calls for your write after inspecting the code, will actually look like this:

<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.

That is not going to be fun to test. So, this brings us to the next solution you can try out; Mock json.dump.

您不应该测试json.dump,应该测试使用正确的参数调用它.话虽如此,您可以按照类似的方式进行嘲笑,并执行以下操作:

You shouldn't be testing json.dump, you should be testing calling it with the right parameters. With that being said, you can follow similar fashion with your mocking and do something like this:

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

现在,您可以大大简化您的测试代码,以简单地验证该方法是否与您正在测试的数据一起被调用.因此,当您将它们放在一起时,将得到以下内容:

Now, with that, you can significantly simplify your test code, to simply validate that the method gets called with your data that you are testing with. So, with that, when you put it all together, you will have something like this:

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)

如果您希望进一步重构以使您的测试方法更简洁,则还可以将修补程序设置为装饰器,将代码更简洁地保留在方法中:

If you're interested in further refactoring to make your test method a bit cleaner, you could also set up your patching as a decorator, leaving your code cleaner inside the method:

@patch('json.dump')
@patch('builtins.open', new_callable=mock_open())
def test_save_data_to_file(self, m, 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)

在这里,检查是您最好的朋友,以了解在什么步骤处调用了什么方法,以进一步帮助进行测试.祝你好运.

Inspecting is your best friend here, to see what methods are being called at what steps, to further help with the testing. Good luck.

这篇关于类方法中使用的模拟open()函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆