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

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

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

问题描述

我试图模拟在我的类的方法中使用的 open 函数.我发现这个线程 如何模拟一个在 with 语句中使用的 open(使用 Python 中的 Mock 框架)? 但无法解决我的问题.unittest 文档还显示了一个解决方案,它也没有模拟我打开的 https://docs.python.org/3/library/unittest.mock-examples.html#patch-decorators

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 调用 write 的,这是调用 write 的地方.(以下有关建议解决方案的详细信息)
  • 您还应该模拟 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() 函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!

本文标题为:模拟类方法中使用的 open() 函数

基础教程推荐