【问题标题】:Can a python constructor be mocked without mocking other properties of the object?可以在不模拟对象的其他属性的情况下模拟 python 构造函数吗?
【发布时间】:2018-12-06 09:09:20
【问题描述】:

是否可以在继续使用生产版本的其他同名字段/函数的同时模拟 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 mock 的自动创建的属性。

我想要的输出如下:

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__)

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

【问题讨论】:

  • 那些不是子类,不是面向对象编程语言上下文中术语的正常含义;您所拥有的只是属于其他类的属性的类。您通常希望避免这种情况。为什么要在这里嵌套类?
  • 接下来,在创建单元测试时,您通常不会只模拟 __init__。你会模拟所有不是被测单元的东西。如果您认为要模拟该单元的 __init__ 方法的类,请模拟 __init__ 方法调用的内容,而不是方法本身。

标签: python unit-testing mocking python-mock


【解决方案1】:

您也可以伪造课程,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()

【讨论】:

    【解决方案2】:

    我同意 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.
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多