Может ли конструктор python быть посмеянным без насмешки над другими свойствами объекта? - PullRequest
0 голосов
/ 27 июня 2018

Можно ли издеваться над конструктором python, продолжая использовать производственную версию других полей / функций с тем же именем? Например, с учетом производственного кода:

class MyClass:
    class SubClass:
        def __init__(self) -> None:
            print("\nreal sub init called")

        class SubSubClass:
            def __init__(self) -> None:
                print("\nreal sub sub init called")

и следующий код теста:

class FakeSubClass:
    def __init__(self) -> None:
        print("\nfake init called")


def test():
    MyClass.SubClass()
    MyClass.SubClass.SubSubClass()

    MyClass.SubClass = Mock(side_effect=FakeSubClass)

    MyClass.SubClass()
    MyClass.SubClass.SubSubClass()

получаем следующий вывод:

real sub init called

real sub sub init called

fake init called

Обратите внимание, что последняя строка MyClass.SubClass.SubSubClass() не создала настоящий SubSubClass, потому что на данный момент это автоматически созданное свойство макета SubClass.

Мой желаемый вывод следующий:

real sub init called

real sub sub init called

fake init called

real sub sub init called

Другими словами, я хочу высмеивать ТОЛЬКО подкласс, но не подкласс. Вещи, которые я пробовал вместо насмешливой строки выше (оба из которых не работают):

MyClass.SubClass.__init__ = Mock(side_effect=FakeSubClass.__init__)

MyClass.SubClass.__new__ = Mock(side_effect=FakeSubClass.__new__)

Обратите внимание, что мне известно о нескольких способах рефакторинга кода, чтобы избежать этой проблемы, но, к сожалению, код не может быть реорганизован.

Ответы [ 2 ]

0 голосов
/ 09 июля 2018

Я согласен, что у ZhouQuan очень хороший ответ, так как он будет работать для любого метода или переменной на MyClass.Subclass. Тем не менее, вот некоторые варианты, которые могут или не могут быть полезны.

Если по какой-либо причине FakeSubClass не может быть отредактирован напрямую или вы хотите наследовать только SubSubClass(), но не более того, его можно изменить в test() следующим образом.

def test():
    MyClass.SubClass()
    MyClass.SubClass.SubSubClass()

    FakeSubClass.SubSubClass = MyClass.SubClass.SubSubClass()

    MyClass.SubClass = Mock(side_effect=FakeSubClass)

    MyClass.SubClass()
    MyClass.SubClass.SubSubClass()

Думаю, стоит отметить, что Mock принимает аргумент wraps, который можно использовать для получения аналогичного поведения, хотя это не совсем то, о чем вы просили. Вот пример.

from unittest.mock import Mock

class MyClass:
    class SubClass:
        def __init__(self) -> None:
            print("\nreal sub init called")

        class SubSubClass:
            def __init__(self) -> None:
                print("\nreal sub sub init called")


class FakeSubClass:
    def __init__(self) -> None:
        print("\nfake init called")

def test():
    MyClass.SubClass()
    MyClass.SubClass.SubSubClass()

    MyClass.SubClass = Mock(side_effect=FakeSubClass, wraps=MyClass.SubClass)

    MyClass.SubClass()
    MyClass.SubClass.SubSubClass()

Это дает другой вывод.

real sub init called

real sub sub init called

fake init called # A call to MyClass.SubClass() causes both real and fake inits.

real sub init called # Same MyClass.SubClass() call.

real sub sub init called # But now the SubSubClass() does resolve correctly.
0 голосов
/ 05 июля 2018

Вы также можете подделать класс, вещь, которую MyClass.SubClass.SubSubClass() не будет работать в вашем случае, заключается в том, что MyClass.SubClass - это Mock, не имеющий определения SubSubClass. Просто позвольте FakeSubClass наследовать его от MyClass.SubClass решит проблему.

И вы можете легко пропатчить MyClass до FakeClass, и у вас будет правильный тестовый объект, а не реальный объект.

from unittest.mock import Mock


class MyClass:
    class SubClass:
        def __init__(self) -> None:
            print("\nreal sub init called")

        class SubSubClass:
            def __init__(self) -> None:
                print("\nreal sub sub init called")


class FakeSubClass(MyClass.SubClass, Mock):
    def __init__(self) -> None:
        print("\nfake init called")


class FakeClass:
    class SubClass(FakeSubClass):
        pass


def test():
    MyClass.SubClass()
    MyClass.SubClass.SubSubClass()

    MyClass.SubClass = Mock(side_effect=FakeSubClass)

    FakeClass.SubClass()
    FakeClass.SubClass.SubSubClass()
...