在Python的单元测试框架中修改全局变量

56
我正在编写 Python 的一系列单元测试,其中一些测试依赖于配置变量的值。这些变量存储在全局 Python 配置文件中,并在其他模块中使用。我想为不同配置变量的值编写单元测试,但目前还没有找到方法。
我无法重写我正在测试的方法的签名。
这是我想实现的内容:
from my_module import my_function_with_global_var

class TestSomething(self.unittest):

    def test_first_case(self):
         from config import MY_CONFIG_VARIABLE
         MY_CONFIG_VARIABLE = True
         self.assertEqual(my_function_with_global_var(), "First result")

    def test_second_case(self):
         from config import MY_CONFIG_VARIABLE
         MY_CONFIG_VARIABLE = False
         self.assertEqual(my_function_with_global_var(), "Second result")

谢谢。

编辑:使示例代码更加明确。

3个回答

84

您可能希望对那些全局变量进行模拟。这样的好处是,在完成后全局变量会被重置。Python自带一个Mocking模块,让您可以这样做。

unittest.mock.patch可以用作装饰器:

class TestSomething(self.unittest):

    @patch('config.MY_CONFIG_VARIABLE', True)
    def test_first_case(self):
         self.assertEqual(my_function_with_global_var(), "First result")

您也可以将其用作上下文管理器:

    def test_first_case(self):
        with patch('config.MY_CONFIG_VARIABLE', True):
            self.assertEqual(my_function_with_global_var(), "First result")

非常棒的方法:简单而有效。感谢分享。特别是在 @Michele 的编辑下更加有用。 - fedorqui
1
在我的情况下,我并不关心初始值,但需要验证全局变量是否被写入。这可以使用 with patch('config') as mock_configself.assertEqual(True, mock_config.MY_CONFIG_VARIABLE) 实现。 - codehearts
是否可以采用类似的方法来修补实例方法中的局部变量? - Naveen
1
当全局变量被用作函数的默认参数值时,似乎这种方法不起作用,例如 def my_function_with_global_var(s=MY_CONFIG_VARIABLE) - sshow
这对我没有起作用,因为我的被测试函数在不同的模块中导入了全局变量。我正在修补定义全局变量的模块,但你必须修补导入全局变量的模块(在我的情况下是我正在测试的函数所在的模块)。 - Alan

62

如果您可以使用@Flimm的回答中提到的unittest.mock.patch,请像那个回答中那样使用它。

from my_module import my_function_with_global_var

但是这个:

import my_module

接下来,您可以将MY_CONFIG_VARIABLE注入导入的my_module中,而无需更改被测试系统,如下所示:

class TestSomething(unittest.TestCase): # Fixed that for you!

    def test_first_case(self):
         my_module.MY_CONFIG_VARIABLE = True
         self.assertEqual(my_module.my_function_with_global_var(), "First result")

    def test_second_case(self):
         my_module.MY_CONFIG_VARIABLE = False
         self.assertEqual(my_module.my_function_with_global_var(), "Second result")

我在回答如何为pyunit模拟输入到标准输入?的问题中做过类似的事情。


1
@badzil:太好了。现在你有了单元测试覆盖率,就可以移除全局变量的需求了;-) - johnsyweb
在Python中使用全局变量来定义常量是否有任何缺点呢?我读到过,如果将它们放在导入模块的__init__中是可以的。 - Kartoch
31
这种方法存在一个微妙的 bug,因为 my_module.MY_CONFIG_VARIABLE 的值在测试完成时没有重置。如果后续运行在同一进程中的测试使用了 MY_CONFIG_VARIABLE 但没有预料到它已经被更改,那么你的测试结果可能会根据它们被执行的顺序而失败或通过。 - ForeverWintr
8
没错,这是一个微妙但重要的缺陷,并且不是我在2017年所采用的方法。当我写下这个答案时,我不确定mock.patch甚至是否已经随Python一起发布了(指2011年)。 - johnsyweb
1
@Johnsyweb,更新一下答案怎么样?我会把我的踩转成赞,因为我在2017年来到了这里 :) - Reut Sharabani
显示剩余2条评论

2

你的代码将MY_CONFIG_VARIABLE引入本地作用域,然后立即使用另一个对象覆盖了该名称。这不会更改config模块中的值。尝试:

import config
config.MY_CONFIG_VARIABLE = False

改用其他方法。


2
也许我忘了提到其他事情。我正在测试的函数位于单独的模块中,因此已经导入了我需要修改的全局变量。您提出的修改仅修改测试文件中的全局变量。 - badzil
1
@badzil:可能最好的选择不是在你正在测试的模块中使用“from config import MY_CONFIG_VARIABLE”,而是使用“import config”并将变量作为“config.MY_CONFIG_VARIABLE”访问。 - Sven Marnach
谢谢。这个方法可行,尽管我本来想不修改我正在测试的模块。我猜测由于被测试的模块使用了import ... from ...指令,所以在导入后无法更改全局变量的值。 - badzil
@badzil:如果值是不可变的(例如示例中的bool),那么您无法更改它。这在某种程度上是不可变对象的本质。--另一种选择是在更改config中的值后重新加载(reload())和重新导入所有模块。 - Sven Marnach

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接