Python:如何在单元测试期间覆盖复杂的函数?

问题描述 投票:0回答:4

我正在使用 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 mocking python-unittest
4个回答
8
投票

最终我使用了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())

这正如我想要的那样:

  1. 我可以用更简单的函数替换函数,这样我可以更轻松地进行测试,
  2. 我不需要以任何方式改变我的原始代码(就像我正在尝试的那样——这是通过传入函数指针来有效地实现的),
    mock
    模块让我可以对内部进行后编码访问。

感谢奥斯汀建议使用

mock
。 顺便说一句,我使用的是 Python 2.7,因此使用了 PyPI 中的
pip
可安装
mock


3
投票

不要尝试用

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

0
投票

在您的代码中,您不应该能够像这样覆盖

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

0
投票

我无法从已接受的答案中使

@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
© www.soinside.com 2019 - 2024. All rights reserved.