类方法中使用的模拟open()函数 [英] Mock open() function used in a class method
问题描述
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 notmymmodule.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_callable
和as
,因此您只需执行以下操作:
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__
方法内部完成.因此,您想从m
的return_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屋!