如何模拟未初始化的全局变量?

How to mock an uninitialized global variable?

我的模块中有一个未初始化的全局变量,该变量在应用程序启动期间已正确初始化。对于类型检查,我使用的语法 var: Type 没有来自 python >= 3.6 的值,因此我不必使默认值(即 None)的类型检查复杂化。

现在我想对使用此全局变量的另一个函数进行单元测试,但我从 unittest.mock.patch 收到错误消息。这是我正在做的事情的简化版本:

global_var: bool
#global_var: bool = False
def init(val: bool) -> None:
    global global_var
    global_var = val
def do_stuff() -> str:
    return "a" if global_var else "b"
import unittest.mock, mod
class MockUninitializedGlobal(unittest.TestCase):
    def test_it(self):
        with unittest.mock.patch("mod.global_var", True):
            actual = mod.do_stuff()
        expected = "a"
        self.assertEqual(expected, actual)

例外是

Traceback (most recent call last):
  File "/home/luc/soquestion/test.py", line 4, in test_it
    with mock.patch("mod.global_var", True):
  File "/nix/store/vs4vj1yzqj1bkcqkf3b6sxm6jfy1gb4j-python3-3.7.7/lib/python3.7/unittest/mock.py", line 1323, in __enter__
    original, local = self.get_original()
  File "/nix/store/vs4vj1yzqj1bkcqkf3b6sxm6jfy1gb4j-python3-3.7.7/lib/python3.7/unittest/mock.py", line 1297, in get_original
    "%s does not have the attribute %r" % (target, name)
AttributeError: <module 'mod' from '/home/luc/soquestion/mod.py'> does not have the attribute 'global_var'

如果我在 mod.py 中注释第 1 行并取消注释第 2 行,则测试通过。

有没有办法在我的应用程序代码中不为全局变量定义默认值的情况下通过测试?


编辑:如评论中所述,实际变量是一个已解析的配置文件,我不想在测试期间加载它。它用于命令行应用程序,因此该变量总是在命令行解析完成后设置。真正的代码是here and here。在实际测试中,我并没有尝试将全局变量直接模拟为 bool,而是模拟配置对象上的某些属性。

我在 unittest.TestCase class 上使用了 setUptearDown 方法来解决这个问题。

根据我上面的编辑,它实际上是一个配置对象,代码如下所示:

class Config:
    def __init__(self, filename: str):
        "Load config file and set self.stuff"
        self.stuff = _code_to_parse(filename)
config: Config
def do_stuff() -> str:
    return "a" if config.stuff else "b"

并且测试在测试之前创建一个模拟配置对象,以便在测试期间模拟配置对象上的某些属性。在拆解中,我还再次删除了对象,这样 nothing 就会溢出到下一个测试用例。

我需要的属性的实际模拟然后在实际测试中的 with 块中完成,因为它 (a) 更干净并且 (b) 与模拟相比,我可以只模拟我需要的东西setUp 方法中配置对象的所有属性。

import unittest, unittest.mock, mod
class MockUninitializedGlobal(unittest.TestCase):
    def setUp(self):
        mod.config = unittest.mock.Mock(spec=mod.Config)
    def tearDown(self):
        del mod.config
    def test_it(self):
        with unittest.mock.patch("mod.config.stuff", True):
            actual = mod.do_stuff()
        expected = "a"
        self.assertEqual(expected, actual)