在单元测试中避免 运行 顶级模块代码
Avoiding running top-level module code in unit test
我正在尝试对一些导入模块的 Python3 代码进行单元测试。不幸的是,模块的编写方式,简单地导入它会产生令人不快的副作用,这对测试来说并不重要。我正在尝试使用 unitest.mock.patch
来绕过它,但运气不佳。
这里是示例的结构:
.
└── work
├── __init__.py
├── test_work.py
├── work.py
└── work_caller.py
__init__.py
是一个空文件
work.py
import os
def work_on():
path = os.getcwd()
print(f"Working on {path}")
return path
def unpleasant_side_effect():
print("I am an unpleasant side effect of importing this module")
# Note that this is called simply by importing this file
unpleasant_side_effect()
work_caller.py
from work.work import work_on
class WorkCaller:
def call_work(self):
# Do important stuff that I want to test here
# This call I don't care about in the test, but it needs to be called
work_on()
test_work.py
from unittest import TestCase, mock
from work.work_caller import WorkCaller
class TestWorkMockingModule(TestCase):
def test_workcaller(self):
with mock.patch("work.work.unpleasant_side_effect") as mocked_function:
sut = WorkCaller()
sut.call_work()
在 work_caller.py
中,我只想测试开始代码,而不是对 work_on()
的调用。当我 运行 测试时,我得到以下输出:
paul-> python -m unittest
I am an unpleasant side effect of importing this module
Working on /Users/paul/src/patch-test
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
我原以为行 I am an unpleasant side effect of importing this module
不会被打印出来,因为函数 unpleasant_side_effect
会被模拟。我可能哪里出错了?
unpleasant_side_effect
是 运行 有两个原因。首先是因为导入是在测试用例开始之前处理的,因此在导入发生时不会被模拟。其次,因为模拟本身会导入 work.py
,因此 运行s unpleasant_side_effect
即使未导入 work_caller.py
。
导入问题可以通过模拟模块 work.py
本身来解决。这可以在测试模块或测试用例本身中全局完成。这里我给它赋了一个MagicMock
,可以导入,调用等
test_work.py
from unittest import TestCase, mock
class TestWorkMockingModule(TestCase):
def test_workcaller(self):
import sys
sys.modules['work.work'] = mock.MagicMock()
from work.work_caller import WorkCaller
sut = WorkCaller()
sut.call_work()
缺点是 work_on 也被嘲笑了,我不确定你的情况是否有问题。
不可能在导入时不运行整个模块,因为函数和类也是语句,因此模块执行必须在返回到调用者之前完成,其中一个人想要更改导入的模块。
如果您部分询问最佳实践。
您应该始终将您的代码拆分为所有其他代码和副作用行使用的库。并且可能通过调用你的副作用代码来消除副作用 def main():
但是如果你想保留副作用,那么你可以这样做:
work_lib.py
:
...no side-effects...
work.py
from work_lib import ...
...side-effects....
test_work.py
from work_lib import ...
我正在尝试对一些导入模块的 Python3 代码进行单元测试。不幸的是,模块的编写方式,简单地导入它会产生令人不快的副作用,这对测试来说并不重要。我正在尝试使用 unitest.mock.patch
来绕过它,但运气不佳。
这里是示例的结构:
.
└── work
├── __init__.py
├── test_work.py
├── work.py
└── work_caller.py
__init__.py
是一个空文件
work.py
import os
def work_on():
path = os.getcwd()
print(f"Working on {path}")
return path
def unpleasant_side_effect():
print("I am an unpleasant side effect of importing this module")
# Note that this is called simply by importing this file
unpleasant_side_effect()
work_caller.py
from work.work import work_on
class WorkCaller:
def call_work(self):
# Do important stuff that I want to test here
# This call I don't care about in the test, but it needs to be called
work_on()
test_work.py
from unittest import TestCase, mock
from work.work_caller import WorkCaller
class TestWorkMockingModule(TestCase):
def test_workcaller(self):
with mock.patch("work.work.unpleasant_side_effect") as mocked_function:
sut = WorkCaller()
sut.call_work()
在 work_caller.py
中,我只想测试开始代码,而不是对 work_on()
的调用。当我 运行 测试时,我得到以下输出:
paul-> python -m unittest
I am an unpleasant side effect of importing this module
Working on /Users/paul/src/patch-test
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
我原以为行 I am an unpleasant side effect of importing this module
不会被打印出来,因为函数 unpleasant_side_effect
会被模拟。我可能哪里出错了?
unpleasant_side_effect
是 运行 有两个原因。首先是因为导入是在测试用例开始之前处理的,因此在导入发生时不会被模拟。其次,因为模拟本身会导入 work.py
,因此 运行s unpleasant_side_effect
即使未导入 work_caller.py
。
导入问题可以通过模拟模块 work.py
本身来解决。这可以在测试模块或测试用例本身中全局完成。这里我给它赋了一个MagicMock
,可以导入,调用等
test_work.py
from unittest import TestCase, mock
class TestWorkMockingModule(TestCase):
def test_workcaller(self):
import sys
sys.modules['work.work'] = mock.MagicMock()
from work.work_caller import WorkCaller
sut = WorkCaller()
sut.call_work()
缺点是 work_on 也被嘲笑了,我不确定你的情况是否有问题。
不可能在导入时不运行整个模块,因为函数和类也是语句,因此模块执行必须在返回到调用者之前完成,其中一个人想要更改导入的模块。
如果您部分询问最佳实践。
您应该始终将您的代码拆分为所有其他代码和副作用行使用的库。并且可能通过调用你的副作用代码来消除副作用 def main():
但是如果你想保留副作用,那么你可以这样做:
work_lib.py
:
...no side-effects...
work.py
from work_lib import ...
...side-effects....
test_work.py
from work_lib import ...