![](/img/trans.png)
[英]How to mock a function, that is imported within an imported method from different module
[英]How to mock a function of a class in an imported module but outside the scope of a function?
我想弄清楚如何使用@patch.object
模擬__init__
和log.write()
用於Logger
class,它已導入但未在模塊的 function 內部使用。 像這樣的教程 https://www.pythontutorial.net/python-unit-testing/python-patch/指出補丁需要在使用它的目標,而不是它來自的地方。 但是,使用的每個示例都顯示了要在另一個 function 中模擬的目標。
在下面提到的用例中,記錄器被導入並用於在 function 的 scope 之外寫入日志。有沒有辦法在main.py
和routers.py
中模擬行為?
源代碼/apis/main.py
from utils.log import Logger
from routes import route
log = Logger(name="logger-1")
log.write("logger started")
def main():
log = Logger(name="logger-1")
log.write("inside main")
route()
if __name__ == "__main__":
import logging
logging.basicConfig(level=logging.INFO) # for demo
main()
在 src/apis/routers/routes.py
from utils.log import Logger
log = Logger(name="logger-1")
log.write, message=f"Inside route")
def route():
log.write, message=f"Logging done.")
在 utils/log/logging.py 中
import logging
Class Logger:
def __init__(self, name):
# needs to be mocked
def write(self, message):
# needs to be mocked to return None
提出問題時,提供一個Minimal Reproducible Example非常方便。 因此,刪除不必要的 fastapi 和 starlette,並提供您嘗試編寫的測試代碼。
這里是:
# file: so74695297_main.py
from so74695297_log import Logger
from so74695297_routes import my_route
log = Logger(name="logger-1")
log.write("logger started")
def main(): # FAKE IMPL
log.write(message=f"in main()")
my_route()
if __name__ == "__main__":
import logging
logging.basicConfig(level=logging.INFO) # for demo
main()
# file: so74695297_routes.py
from so74695297_log import Logger
log = Logger(name="logger-1")
def my_route(): # FAKE IMPL
log.write(message=f"route")
# file: so74695297_log.py
import logging
class Logger:
def __init__(self, name):
self._logger = logging.getLogger(name) # FAKE IMPL
def write(self, message):
self._logger.info(message) # FAKE IMPL
運行時( main.py
文件做了一些事情):
INFO:logger-1:in main()
INFO:logger-1:route
當記錄器不喜歡任何格式化程序時,這是預期的 output。
然后添加測試:
# file: so74695297_test.py
import unittest
import unittest.mock as mock
from so74695297_routes import my_route
class TestMyRoute(unittest.TestCase):
def test__my_route_write_a_log(self):
spy_logger = mock.Mock()
with mock.patch("so74695297_log.Logger", new=spy_logger):
my_route()
assert spy_logger.assert_called()
if __name__ == "__main__":
unittest.main() # for demo
Ran 1 test in 0.010s
FAILED (failures=1)
Failure
Traceback (most recent call last):
File "/home/stack_overflow/so74695297_test.py", line 12, in test__my_route_write_a_log
assert spy_logger.assert_called()
File "/usr/lib/python3.8/unittest/mock.py", line 882, in assert_called
raise AssertionError(msg)
AssertionError: Expected 'mock' to have been called.
現在我們有事情要做了!
正如@MrBeanBremen 指出的那樣,您的記錄器是在導入時配置的(即使不是“主”模塊)這一事實使事情變得復雜。
問題是,當mock.patch
行運行時,模塊已經被導入並創建了它們的 Logger。 我們可以做的是模擬Logger.write
方法:
def test__my_route_writes_a_log(self):
with mock.patch("so74695297_log.Logger.write") as spy__Logger_write:
my_route()
spy__Logger_write.assert_called_once_with(message="route")
Ran 1 test in 0.001s
OK
如果您更喜歡使用裝飾器形式:
@mock.patch("so74695297_log.Logger.write")
def test__my_route_writes_a_log(self, spy__Logger_write):
my_route()
spy__Logger_write.assert_called_once_with(message="route")
因為我們模擬了類的方法,所以每個 Logger 實例都有一個write
的模擬版本:
# vvvv
@mock.patch("so74695297_main.Logger.write")
def test__main_writes_a_log(self, spy__Logger_write):
main()
# assert False, spy__Logger_write.mock_calls
spy__Logger_write.assert_any_call(message="in main()")
最后, main.Logger.write
與routes.Logger.write
和log.Logger.write
本質上是一樣的,只是對相同“方法”object 的引用。從一種方式模擬,也為所有其他方式模擬.
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.