用 Python 打补丁

时间:2021-07-05 14:01:24

标签: python unit-testing mocking patch

我有一个 python 文件说 python_file_a.py

def load_content():
    dir = "/down/model/"
    model = Model(model_dir=dir)
    return model

model = load_content()

def invoke(req):
    return model.execute(req)

test_python_file_a.py

@patch("module.python_file_a.load_content")
@patch("module.python_file_a.model", Mock(spec=Model))
def test_invoke():
    from module.python_file_a import model, invoke
    model.execute = Mock(return_value="Some response")
    invoke("some request")

这还是在测试中尝试从路径“/down/model/”加载实际模型。什么是正确的修补方法,以便在测试中模拟 load_content 函数?

1 个答案:

答案 0 :(得分:0)

如果不了解更多关于您的代码的作用或使用方式的信息,就很难说清楚,但在这种情况下,正确的方法 - 在许多情况下 - 是不要将值硬编码为函数中的局部变量。更改您的 load_content() 函数以采用如下参数:

def load_content(dirname):
   ...

甚至给它一个像

这样的默认值
def load_content(dirname="/default/path"):
    pass

对于测试,不要使用在模块级别实例化的 model 实例(可以说您一开始不应该这样做,但这同样取决于您要尝试做什么)。

更新:仔细检查后,问题似乎确实源于您在导入时实例化模块全局实例。也许尽量避免这样做,而是使用延迟实例化,例如:

model = None

那么如果你真的必须写一个访问全局变量的函数:

def invoke():
    global model
    if model is None:
        model = load_content()

或者,您可以使用 PEP 562 模块级 __getattr__ 函数。

或者编写一个类,而不是将所有内容都放在模块级别。

class ModelInvoker:
    def __init__(self, dirname='/path/to/content'):
        self.dirname = dirname

    @functools.cached_property
    def model(self):
        return load_content(self.dirname)

    def invoke(self, req):
        return model.execute(req)

许多其他方法取决于您的用例。但是,如果您希望能够轻松地模拟和替换某些代码的一部分,并且不想在导入时不必要地执行代码,那么您需要找到某种形式的封装。