可以在不模拟对象其他属性的情况下模拟python构造函数吗?

时间:2018-06-27 12:53:43

标签: python unit-testing mocking python-mock

在继续使用生产版本的其他同名字段/函数时,是否可以模拟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

换句话说,我只想模拟SubClass,而不是SubSubClass。我尝试了代替上面的模拟行的方法(两者均无效):

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

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

请注意,我知道可以通过几种方式来重构代码,以避免出现此问题,但遗憾的是,无法重构代码。

2 个答案:

答案 0 :(得分:2)

您还可以伪造该类,MyClass.SubClass.SubSubClass()在您的情况下不起作用,是MyClass.SubClass是一个没有SubSubClass定义的Mock。只需让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()

答案 1 :(得分:1)

我同意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参数,尽管它并不是您所要的,但它可以用来获得 like 行为。对于。这是一个例子。

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.