将闭包传递给函数中的FunctionType

时间:2019-03-21 14:23:00

标签: python testing closures

我有这样的代码:

class A():
    def __init__(self, a):
        self.a = a

    def outer_method(self):
        def inner_method():
            return self.a +1
        return inner_method()

我想为inner_method编写测试。为此,我正在使用这样的代码:

def find_nested_func(parent, child_name):
    """ 
       Return the function named <child_name> that is defined inside
        a <parent> function
        Returns None if nonexistent
    """
    consts = parent.__code__.co_consts
    item = list(filter(lambda x:isinstance(x, CodeType) and x.co_name==child_name, consts ))[0]
    return FunctionType(item, globals())

使用find_nested_func(A().outer_method, 'inner_method')调用它,但是在调用“ FunctionType”时失败,因为无法创建函数,因为在函数停止为内部函数的那一刻,“ self.a”停止存在。我知道构造FunctionType可以将可以解决此问题的闭包作为参数来接受,但是我不知道如何使用它。我该如何通过?

它给出的错误是下一个错误:

    return FunctionType(item, globals())
TypeError: arg 5 (closure) must be tuple

1 个答案:

答案 0 :(得分:1)

您为什么要测试inner_method?在大多数情况下,您应该只测试公共API的一部分。 outer_methodA的公共API的一部分,因此请对其进行测试。 inner_method是可以更改的实现细节:如果您决定重命名该怎么办?如果在不修改outer_method的外部可见行为的情况下稍微对其进行重构,该怎么办?类A的用户没有(简单)的方法来调用inner_method。单元测试通常仅用于测试您的类用户可以调用的东西(我假设这些是用于单元测试的,因为集成测试这种细粒度的方法很奇怪,并且大多数情况下仍然适用相同的原理)。

实际上,提取另一个函数范围内定义的函数会遇到问题,原因包括变量捕获。您无法知道inner_method仅捕获self还是outer_method执行某些逻辑并计算inner_method使用的某些变量。例如:

class A:
    def outer_method():
        b = 1

        def inner_method():
            return self.a + b

        return inner_method()

此外,您可能在函数定义周围有控制语句,因此如果不运行outer_method,就无法确定使用哪个定义。例如:

import random

class A:
    def outer_method():
        if random.random() < 0.5:
            def inner_method():
                return self.a + 1
        else:
            def inner_method():
                return self.a + 2

        return inner_method()

您无法在此处提取inner_method,因为其中有两个,并且在运行outer_method之前您不知道实际使用了哪个。

因此,请勿测试inner_method

如果inner_method确实非常复杂,以至于您想单独进行测试(如果这样做,则有原则的测试表明您应该模拟出它的用法,例如,在outer_method中的用法),然后只需在A上将其设为“私密的”方法即可:

class A:
    def _inner_method(self):
        return self.a + 1
    def outer_method(self):
        return self._inner_method()

有原则的测试表明,您实际上不应该测试下划线方法,但有时有必要这样做。通过这种方式,您可以像测试其他任何方法一样测试_inner_method。然后,在测试outer_method时,可以通过执行a._inner_method = Mock()(其中a是受测试的A对象)来模拟它。

此外,使用class A。除非您有父类,否则不需要括号。