如何模拟基于* args输入返回值的函数?

时间:2019-12-12 18:58:58

标签: python unit-testing mocking pytest

我有一个函数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个可能的参数可以传递给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

但是我不知道如何在测试环境中实现此替代。

谢谢。

2 个答案:

答案 0 :(得分:0)

您可以从另一个函数中调用一个函数,然后在设置数据库后将其替换。这样,您仍然可以在代码中的任何地方调用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

答案 1 :(得分:0)

unittest.mock.patch是你的朋友。

您没有指定模块名称,因此我在其中放置了一些占位符。

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替换为该函数的模拟版本。替换是在模块级别完成的,因此模块<module_with_foo>中调用get_info_from_tags的所有内容现在都将调用您的模拟。


请注意给patch 的路径:

patch替换模块属性的值。因此,如果您有一个带有功能moo的模块foo,该模块会从模块bar调用moo2

# moo module
from moo2 import bar

def foo():
    bar()

...从patch的角度来看,moo.foo呼叫moo.bar,而不是moo2.bar。这就是为什么您必须在使用该功能的模块而不是定义该模块的地方修补该模块。