【问题标题】:How to mock a function that returns values based on its *args input?如何模拟基于 *args 输入返回值的函数?
【发布时间】:2019-12-12 18:58:58
【问题描述】:

我有一个函数 foo 调用另一个函数 get_info_from_tags

这是get_info_from_tags 的实现:

def get_info_from_tags(*args):
    instance_id = get_instance_id()
    proc = subprocess.Popen(["aws", "ec2", "describe-tags", "--filters", f"Name=resource-id,Values={instance_id}"],
                            stdout=subprocess.PIPE, shell=True)
    (out, err) = proc.communicate()
    em_json = json.loads(out.decode('utf8'))
    tags = em_json['Tags']  # tags list

    results = []

    for arg in args:
        for tag in tags:
            if tag['Key'] == arg:
                results.append(tag['Value'])

    return results

有一组 10 个可能的 args 可以传递给get_info_from_tags,我需要返回正确的数组(我不想调用 aws 服务,这就是我模拟的重点,我将手动设置字典中的值)。

如何模拟get_info_from_tags,以便在我调用时

get_info_from_tags('key1', 'key2' ...)

在 foo 函数内部,我得到了我想要的结果?

我已经尝试过pytest的一些功能,但似乎不太明白。

一个可能的解决方案是创建另一个函数:


def mocked_get_info_from_tags(*args):
    values = []
    for arg in args:
        values.append(my_dictionary[arg])
    return values

但我不知道如何在测试环境中实现此覆盖。

谢谢。

【问题讨论】:

  • 鉴于您对get_info_from_db 的实现,我不明白为什么您需要模拟该函数而不是db_connector.get。你能解释一下吗?
  • 这是因为,为了简单起见,我在这里写了一个我的实际函数的抽象,它做了比这更复杂的事情。但核心思想是一样的。例如,在我的实际函数中,我调用 subprocess 并运行一些命令。仅模拟 db_connector.get 会很复杂,因为在调用它之前,它会运行 subprocess 命令。
  • 如果你的脚本的某个MOCK属性设置为True,你可以在真实条件下使用return [db_connector.get(arg) for arg in args]return[mock_values(arg) for arg in args]
  • 好的,我明白了,不写实际的函数会导致我没想到的答案。我将使用实际功能编辑问题。
  • @TeodoroMendes 我明白了,但是是什么阻止你嘲笑那个功能呢?您只需要为补丁提供模拟版本。您能否更具体地说明实际问题是什么?

标签: python unit-testing mocking pytest


【解决方案1】:

unittest.mock.patch 是你的朋友。

你没有指定模块名称,所以我在里面放了一些<placeholders>

from unittest.mock import patch
from <module_with_foo> import foo
from <module_with_mock> import mocked_get_info_from_tags

with patch('<module_with_foo>.get_info_from_tags', mocked_get_info_from_tags):
    foo()

这会将get_info_from_tags 替换为此函数的模拟版本。替换是在模块级别完成的,因此模块 &lt;module_with_foo&gt; 中调用 get_info_from_tags 的所有内容现在都将调用您的模拟。


注意patch的路径

patch 替换模块属性的值。所以,如果你有一个模块moo 和一个函数foo,它从模块moo2 调用bar

# moo module
from moo2 import bar

def foo():
    bar()

...从patch 的角度来看,moo.foo 调用moo.bar,而不是moo2.bar。这就是为什么你必须修补使用函数的模块,而不是定义函数的地方。

【讨论】:

    【解决方案2】:

    您可以从另一个函数调用一个函数,然后在您设置数据库时替换。这样你仍然可以在代码中的任何地方调用foo.get_info_from_db('key1', 'key2' ...),当你添加正确的数据库连接时,你所要做的就是改变一个主要的get_info_from_db函数实现并删除模拟

    import db_connector
    
    def mocked_get_info_from_db(*args):
        values = []
        for arg in args:
            values.append(my_dictionary[arg])
        return values
    
    def get_info_from_db(*args):
    
        # remove this once your database is setup
        return mocked_get_info_from_db(*args)
    
        # values = []
        # for arg in args:
        #     values.append(db_connector.get(arg))
        # return values
    

    【讨论】:

    • 谢谢,但此代码也必须由其他开发人员测试。所以我需要搭建一个测试环境。
    猜你喜欢
    • 2013-04-16
    • 1970-01-01
    • 2021-11-27
    • 1970-01-01
    • 2021-03-15
    • 1970-01-01
    • 2020-04-06
    • 1970-01-01
    • 2019-06-15
    相关资源
    最近更新 更多