Python 如何使用模拟库测试非纯(包含副作用)递归图遍历函数?

Python 如何使用模拟库测试非纯(包含副作用)递归图遍历函数?,python,unit-testing,mocking,depth-first-search,directed-acyclic-graphs,Python,Unit Testing,Mocking,Depth First Search,Directed Acyclic Graphs,我有一个深度优先搜索算法函数,可以递归遍历DAG(有向循环图),找到父节点和子节点之间的依赖关系,然后最终返回这些依赖关系的列表 对于初始节点中的每个依赖项,我使用另一个类的函数分别从依赖项分配下一个节点,以便能够“检查下一个” 我正在使用Mock为此设置单元测试。但我似乎无法让它工作,因为我正在测试的函数中存在副作用(即,每次迭代都有另一个函数调用)。请参见下面的示例代码: # Simplified version of the DFS algorithm to show necessary

我有一个深度优先搜索算法函数,可以递归遍历DAG(有向循环图),找到父节点和子节点之间的依赖关系,然后最终返回这些依赖关系的列表

对于初始节点中的每个依赖项,我使用另一个类的函数分别从依赖项分配下一个节点,以便能够“检查下一个”

我正在使用Mock为此设置单元测试。但我似乎无法让它工作,因为我正在测试的函数中存在副作用(即,每次迭代都有另一个函数调用)。请参见下面的示例代码:

# Simplified version of the DFS algorithm to show necessary information
def recursive_function(other_class, object_to_check, list_to_return=None):
    if list_to_return is None:
        list_to_return = [object_to_check]

    dependencies = object_to_check.dependencies

    if dependencies :
        for dep in dependencies:

            # this is the function call thats making it problematic
            next_object = other_class.get_next_object_from_dep(dep)

            list_to_return.append(next_object)
            recursive_function(other_class, next_object, list_to_return)

    return list_to_return
如您所见,我正在调用其他类。从我的遍历函数中的\u dep(dep)获取\u next\u object\u。我已经尝试过为此设置单元测试,但无法让它工作

我知道有Mock、MagicMock、设置补丁、设置返回值以及Mock中的副作用(如果有),但我仍然无法将这些拼图拼凑在一起

我当前的测试示例,简化:

MOCK_PARENT= '''
{
    "id": 1337
}
'''

MOCK_CHILD= '''
{
    "id": 1,
    "parent_id": 1337
}
'''

# So testing on MOCK_CHILD should return both MOCK_CHILD 
# and MOCK_PARENT as MOCK_CHILD is dependent on MOCK_PARENT (preceding dependency).

class Test(unittest.TestCase):
    def setUp(self):
        self.mock_parent = MockParentClass(json.loads(MOCK_PARENT))
        self.mock_child = MockChildClass(json.loads(MOCK_CHILD))
        self.parent = ParentClass(self.mock_parent)
        self.child = ChildClass(self.mock_child)

        self.m = Mock()
        self.session = Session()
        self.other_class = OtherClass(self.session)

    # Do I need to add a @mock.patch decorator here?
    def test_graph_traversal(self):
        expected_results = [ChildObject, ParentObject]

        # matches --> "def recursive_function(other_class, object_to_check, list_to_return=None)"
        objects_from_my_func = recursive_function(self.other_class, self.child, [self.child])
        assertEquals(objects_from_my_func, expected_results)

使用这种方法时,我会遇到一些错误,如“模拟对象没有属性函数…”等


本质上,我应该如何考虑测试一个也有副作用的递归函数?我无法解决副作用,因为我需要遍历中的外部函数调用来找到下一个要检查的节点。我对整个单元测试相当陌生,所以使用模拟库是我非常想学习的东西。

也许这两个答案很有帮助。我认为您必须定义触发所有所需执行路径的DAG,然后测试每个DAG情况的可观察行为是否正确。也就是说,你放置的模拟被称为他们应该的。请注意,您可以使用
wrapps
,让mock调用真正的方法,并且仍然监视它们的调用。请参阅@progmatico,谢谢您的回复。但是对于我的用例;我的函数返回我们在其他函数中使用的“节点”(类对象)列表,因此我确实需要测试我的函数是否根据相互依赖的条件返回正确的“节点”。。。因此,按照一个响应的建议,仅测试调用计数是否仍然有效,而如果从函数返回了正确的对象,则测试是否仍然有效?我基本上想做我的函数所做的事情,但是把它全部模拟成假对象/节点,这样它们就不会被创建和迭代。不,那是因为在这种情况下,调用计数是问题的关键部分(确保在n-1次发送失败时有n次重试)。正如我所说的,设置短DAG结构,您知道它将触发算法的所有代码路径,您可以模拟
其他类。从\u dep()
获取\u next\u object\u以返回已知序列中的值。也许这两个答案很有帮助。我认为您必须定义触发所有所需执行路径的DAG,然后测试每个DAG案例的可观察行为是否正确。也就是说,您放置的模拟按其应该的方式调用。请注意,您可以使用
wrapps
,让mock调用真正的方法,并且仍然监视它们的调用。请参阅@progmatico,谢谢您的回复。但是对于我的用例;我的函数返回我们在其他函数中使用的“节点”(类对象)列表,因此我确实需要测试我的函数是否根据相互依赖的条件返回正确的“节点”。。。因此,按照一个响应的建议,仅测试调用计数是否仍然有效,而如果从函数返回了正确的对象,则测试是否仍然有效?我基本上想做我的函数所做的事情,但是把它全部模拟成假对象/节点,这样它们就不会被创建和迭代。不,那是因为在这种情况下,调用计数是问题的关键部分(确保在n-1次发送失败时有n次重试)。正如我所说,设置短DAG结构,您知道它将触发算法的所有代码路径,您可以模拟
其他\u类。从\u dep()
获取\u next\u object\u,也可以从已知序列返回值。