各位大神好,本人想問一個關於Python3的unittest.mock的問題
我在mainFunction.py的main()中用with.open分別寫入file1以及讀取file2兩個檔
亦即mainFunction.py的main()當中有以下code:
with open("path/to/file1", "w+") as f1:
f1.write("message for file1")
with open("path/to/file2", "rb") as f2:
print(f2.read())
sys.exit(0)
以下為我的unit test code test_main.py
import mainFunction
from unittest import mock
class MyUnitTest(unittest.TestCase):
def test_main(self):
with self.assertRaises(SystemExit) as cm, mock.patch("builtins.open",
side_effect = [
mock.mock_open(read_data="").return_value,
mock.mock_open(read_data="read from file2").return_value
]) as mock_files:
mainFunction.main()
mock_files.return_value.__enter__().write.assert_called_once_with("message for file1")
mock_files.return_value.__enter__().read.assert_called_once_with("message for
file2")
self.assertEqual(cm.exception, 0)
經過我的測試"read from file2"是真的有被with open("path/to/f2", "r") as f2 讀取,f2.read()印出來就是"read from file2"
然而 mock_files.return_value.enter() 那幾行皆會出現 Assertion Error:
AssertionError: Expect 'read' to be called once. Called 0 times
AssertionError: Expect 'write' to be called once. Called 0 times
求解如何使用mock_files來驗證傳送至file1的"message for file1",還有寫入file2的訊息是否正確,本人爬了很多stackoverflow的文都還找不到解,感謝!
先上改自你程式碼的我版:
(大致上說明內容都丟註解內了~)
## mainFunction.py
import sys
def main():
with open("path/to/file1", "w+") as f1:
f1.write("message for file1")
with open("path/to/file2", "rb") as f2:
print(f2.read())
sys.exit(0)
##
if __name__ == '__main__': main()
##
## test_main.py
import unittest
from unittest import mock
import mainFunction
##
class MyUnitTest(unittest.TestCase):
def test_main(self):
## 這個 tuple 是因為我需要 MagicMock (created by mock_open()) 的參照(obj-ref);
myHandles = (
mock.mock_open(),
mock.mock_open(read_data="read from file2")
)
## https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock.side_effect
with \
self.assertRaises(SystemExit) as cm, \
mock.patch(
"builtins.open",
side_effect = tuple([
i.return_value for i in myHandles
])
) as mock_files, \
mock.patch("builtins.print") as mock_print \
:
mainFunction.main()
## 檢查 mock_calls:這段是讓你知道 [write()]、[read()] 會發生在哪裡;
for i in mock_files.mock_calls:
print('■[myUT] mock_files.mock_calls:', i)
for i in range(len(myHandles)):
for j in myHandles[i].mock_calls:
print(f'■[myUT] myHandles[{i}].mock_calls:', j)
for i in mock_print.mock_calls:
print('■[myUT] mock_print.mock_calls:', i)
#mock_files.return_value.__enter__().write.assert_called_once_with("message for file1")
myStrs = ['message for file1']
myStrs.append(myStrs[-1] + 'p')
for i in myStrs:
try: ## 用 try 是因為我想測試 i (in myStrs) 的值不同時,會不會觸發例外,並能繼續後續流程;
myHandles[0].return_value .__enter__().write .assert_called_once_with(i)
except Exception as err:
print('■ Exception: ',end=''); print(err)
else : print(f'■ Fine:Str[{i}];')
#mock_files.return_value.__enter__().read.assert_called_once_with("message for file2")
## 上面這行我不懂你想做啥;你檢查 read() 所用的參數你不覺得有問題?
## read() 跟 write() 是不同的動作,參數定義也不同耶……
## 若想驗證 [f2.read()] (@mainFunction.py),我就用 mock_print 驗證 [print(f2.read())] 了;
## (雖說,就 mock 的用途而言,我覺得在一般狀況下想去驗證 mock 的 return_value 的想法有點神奇……)
myStr = 'message for file2'
try: mock_print.assert_called_once_with(myStr)
except Exception as err:
print('■ Exception: ',end=''); print(err)
else : print(f'■ Fine:Str[{myStr}];')
#self.assertEqual(cm.exception, 0)
self.assertEqual(cm.exception.code, 0)
##
if __name__ == '__main__': unittest.main()
##
我的輸出(WinCmd):
> python "./test_main.py"
■[myUT] mock_files.mock_calls: call('path/to/file1', 'w+')
■[myUT] mock_files.mock_calls: call('path/to/file2', 'rb')
■[myUT] myHandles[0].mock_calls: call().__enter__()
■[myUT] myHandles[0].mock_calls: call().write('message for file1')
■[myUT] myHandles[0].mock_calls: call().__exit__(None, None, None)
■[myUT] myHandles[1].mock_calls: call().__enter__()
■[myUT] myHandles[1].mock_calls: call().read()
■[myUT] myHandles[1].mock_calls: call().__exit__(None, None, None)
■[myUT] mock_print.mock_calls: call('read from file2')
■ Fine:Str[message for file1];
■ Exception: expected call not found.
Expected: write('message for file1p')
Actual: write('message for file1')
■ Exception: expected call not found.
Expected: print('message for file2')
Actual: print('read from file2')
.
----------------------------------------------------------------------
Ran 1 test in 0.027s
OK
你說:
還有寫入file2的訊息是否正確
你的程式碼哪裡有寫入 file2 ?我只看到 f2.read()
(該不會是勉強算上 mock 的 side_effect
?這想法對我來說很詭異耶……)
提醒一下,你的程式碼內,mock_files.return_value.__enter__().write.assert_called_once_with("message for file1")
放在 with
內的話,很可能不會被執行喔(因為 mainFunction.py
的 main()
內有 sys.exit()
)。
(如果你是故意的就當我沒說~)
另,請不要像我在 unittest
亂用 try-except
(除非你知道你在做啥);畢竟 unittest
就是檢測,一般狀況下有異常就該觸發例外。我只是作範例給你看才這樣用喔!