Передать замыкание в FunctionType в функции - PullRequest
0 голосов
/ 21 марта 2019

У меня есть такой код:

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 Ответ

1 голос
/ 21 марта 2019

Почему вы пытаетесь проверить inner_method? В большинстве случаев вам следует тестировать только части вашего публичного API. outer_method является частью общедоступного API A, так что проверьте это. 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), тогда просто сделайте его метод private-ish для 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. Паренсы не нужны, если у вас нет родительских классов.

...