問題描述
我試圖模擬在我的類的方法中使用的 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 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() 函數的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,也希望大家多多支持html5模板網!