我正在使用 Python 的
unittest
模块来测试我正在编写的脚本。
脚本包含这样的循环:
// my_script.py
def my_loopy_function():
aggregate_value = 0
for x in range(10):
aggregate_value = aggregate_value + complicated_function(x)
return aggregate_value
def complicated_function(x):
a = do()
b = something()
c = complicated()
return a + b + c
我使用
unittest
测试 complicated_function
没有任何问题。但我想通过覆盖 my_loopy_function
来测试 complicated_function
。
我尝试修改我的脚本,以便
my_loopy_function
将 complicated_function
作为可选参数,以便我可以从测试中传递一个简单版本:
// my_modified_script.py
def my_loopy_function(action_function=None):
if action_function is not None:
complicated_function = action_function
aggregate_value = 0
for x in range(10):
aggregate_value = aggregate_value + complicated_function(x)
return aggregate_value
def complicated_function(x):
a = do()
b = something()
c = complicated()
return a + b + c
// test_my_script.py
from myscript import my_loopy_function
class TestMyScript(unittest.TestCase):
test_loopy_function(self):
def simple_function():
return 1
self.assertEqual(10, my_loopy_function(action_function=simple_function))
它没有像我希望的那样工作,对于我应该如何做有什么建议吗?
最终我使用了Python的
mock
,它允许我重写complicated_function
,而无需以任何方式调整原始代码。
这是原始脚本,请注意,
complicated_function
没有作为“my_loopy_function
”参数传递给action_function
(这是我在之前的解决方案中尝试过的):
// my_script.py
def my_loopy_function():
aggregate_value = 0
for x in range(10):
aggregate_value = aggregate_value + complicated_function(x)
return aggregate_value
def complicated_function(x):
a = do()
b = something()
c = complicated()
return a + b + c
这是我用来测试它的脚本:
// test_my_script.py
import unittest
import mock
from my_script import my_loopy_function
class TestMyModule(unittest.TestCase):
@mock.patch('my_script.complicated_function')
def test_1(self, mocked):
mocked.return_value = 1
self.assertEqual(10, my_loopy_function())
这正如我想要的那样:
mock
模块让我可以对内部进行后编码访问。感谢奥斯汀建议使用
mock
。
顺便说一句,我使用的是 Python 2.7,因此使用了 PyPI 中的 pip
可安装 mock
。
不要尝试用
complicated_function
覆盖 action_function
,只需使用 complicated_function
作为默认值 action_function
:
def my_loopy_function(action_function=complicated_function):
aggregate_value = 0
for x in range(10):
aggregate_value = aggregate_value + action_function(x)
return aggregate_value
在您的代码中,您不应该能够像这样覆盖
complicated_function
。如果我尝试一下,我会得到UnboundLocalError: local variable 'complicated_function' referenced before assignment
。
但也许问题在于,在您的实际代码中,您以其他方式引用
complicated_function
(例如作为模块的成员)?然后,通过在测试中覆盖它,您将覆盖实际的 complicated_function
,因此您将无法在其他测试中使用它。
正确的方法是用全局变量覆盖 local 变量,如下所示:
def my_loopy_function(action_function=None):
if action_function is None:
action_function = complicated_function
aggregate_value = 0
for x in range(10):
# Use action_function here instead of complicated_function
aggregate_value = aggregate_value + action_function(x)
return aggregate_value
我无法从已接受的答案中使
@mock.patch
以任何组合工作(值得注意的是,我正在使用pytest,但答案似乎是与测试库无关,所以我不知道),但我发现了一个更通用的这样做的方法,所以分享。
有一种称为“猴子修补”的技术。它的目的正是覆盖第三方模块的方法。
它的工作原理如下:您首先执行
import some_module
,然后将其功能foo
覆盖为bar
,然后执行some_module.foo = bar
。
重要:模块应导入而不带
from
。 IE。如果您先执行 from some_module import foo
,然后执行 foo = bar
,则不会起作用。
示例:
λ cat some_module.py
def hello():
print("hello")
def some_func():
hello()
λ cat test.py
import some_module
def my_override():
print("hi")
some_module.hello = my_override
some_module.some_func()
λ python3 test.py
hi